@trackunit/react-table-base-components 1.3.131 → 1.3.134

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/index.cjs.js CHANGED
@@ -74,7 +74,7 @@ const cvaIdentityCell = cssClassVarianceUtilities.cvaMerge(["grid", "items-cente
74
74
  density: "default",
75
75
  },
76
76
  });
77
- const cvaIdentityCellLayout = cssClassVarianceUtilities.cvaMerge(["grid", "items-center", "gap-1", "text-sm"], {
77
+ const cvaIdentityCellLayout = cssClassVarianceUtilities.cvaMerge(["grid", "items-center", "text-sm"], {
78
78
  variants: {
79
79
  density: {
80
80
  default: "grid-rows-[min-content_auto]",
@@ -85,7 +85,7 @@ const cvaIdentityCellLayout = cssClassVarianceUtilities.cvaMerge(["grid", "items
85
85
  density: "default",
86
86
  },
87
87
  });
88
- const cvaIdentityCellDetails = cssClassVarianceUtilities.cvaMerge(["flex", "w-full", "min-w-0", "items-center", "truncate", "text-xs", "text-gray-500"], {
88
+ const cvaIdentityCellDetails = cssClassVarianceUtilities.cvaMerge(["flex", "w-full", "min-w-0", "items-center", "truncate", "text-xs", "text-gray-600", "font-medium"], {
89
89
  variants: {
90
90
  density: {
91
91
  default: "pt-0",
@@ -102,7 +102,7 @@ const cvaIdentityCellDetailsItem = cssClassVarianceUtilities.cvaMerge(["last:tru
102
102
  * @returns {ReactElement} LinkCell component
103
103
  */
104
104
  const IdentityCell = ({ className, dataTestId = "identity-cell", density = "default", title, details = [], thumbnail, }) => {
105
- return (jsxRuntime.jsxs("div", { className: cvaIdentityCell({ className, density, hasThumbnail: !!thumbnail }), "data-testid": dataTestId, children: [thumbnail ? (jsxRuntime.jsx("div", { className: "mr-1 flex h-8 w-8 items-center justify-center overflow-hidden rounded-md", children: thumbnail })) : null, jsxRuntime.jsxs("div", { className: cvaIdentityCellLayout({ className, density }), children: [jsxRuntime.jsx("div", { className: "flex w-full min-w-0 truncate text-sm", children: jsxRuntime.jsx(reactComponents.Heading, { className: "truncate text-sm", dataTestId: `${dataTestId}-name`, variant: "tertiary", children: title }) }), details.length > 0 && (jsxRuntime.jsx("div", { className: cvaIdentityCellDetails({ className, density }), children: details.map((value, index, array) => (jsxRuntime.jsxs(react.Fragment, { children: [jsxRuntime.jsx("span", { className: cvaIdentityCellDetailsItem({ className }), children: value }), index < array.length - 1 && (jsxRuntime.jsx("div", { className: "mx-0.5 flex items-center", children: jsxRuntime.jsx(reactComponents.Icon, { className: "w-4 text-neutral-200", color: "neutral", name: "Slash", size: "small" }) }))] }, index))) }))] })] }));
105
+ return (jsxRuntime.jsxs("div", { className: cvaIdentityCell({ className, density, hasThumbnail: !!thumbnail }), "data-testid": dataTestId, children: [thumbnail ? (jsxRuntime.jsx("div", { className: "mr-1 flex h-8 w-8 items-center justify-center overflow-hidden rounded-md", children: thumbnail })) : null, jsxRuntime.jsxs("div", { className: cvaIdentityCellLayout({ className, density }), children: [jsxRuntime.jsx("div", { className: "flex w-full min-w-0 truncate text-sm", children: jsxRuntime.jsx(reactComponents.Heading, { className: "truncate text-sm", dataTestId: `${dataTestId}-name`, variant: "tertiary", children: title }) }), details.length > 0 && (jsxRuntime.jsx("div", { className: cvaIdentityCellDetails({ className, density }), children: details.map((value, index, array) => (jsxRuntime.jsxs(react.Fragment, { children: [jsxRuntime.jsx("span", { className: cvaIdentityCellDetailsItem({ className }), children: value }), index < array.length - 1 && (jsxRuntime.jsx("div", { className: "mx-0.5 flex items-center", children: jsxRuntime.jsx(reactComponents.Icon, { className: "w-4 text-neutral-300", color: "neutral", name: "Slash", size: "small" }) }))] }, index))) }))] })] }));
106
106
  };
107
107
 
108
108
  /**
@@ -255,15 +255,79 @@ const ResizeHandle = ({ isResizing = false, dataTestId, onMouseDown: onMouseDown
255
255
  };
256
256
  return (jsxRuntime.jsx("div", { className: cvaResizeHandel({ isResizing, className }), "data-testid": dataTestId, onMouseDown: event => onMouseDown(event), role: "separator", ...rest }));
257
257
  };
258
- const cvaResizeHandel = cssClassVarianceUtilities.cvaMerge(["absolute", "cursor-col-resize", "right-0", "top-0", "h-full", "w-1", "select-none", "hover:bg-primary-400"], {
258
+ const cvaResizeHandel = cssClassVarianceUtilities.cvaMerge([
259
+ "absolute",
260
+ "cursor-ew-resize",
261
+ "z-2",
262
+ "h-full",
263
+ "w-2",
264
+ "top-0",
265
+ "right-[-3px]",
266
+ "flex",
267
+ "items-center",
268
+ "after:block",
269
+ "after:left-[calc(50%-2px)]",
270
+ "after:h-full",
271
+ "after:z-[1]",
272
+ "after:absolute",
273
+ "after:w-0.5",
274
+ "after:hover:bg-info-500",
275
+ ], {
259
276
  variants: {
260
277
  isResizing: {
261
- true: ["bg-primary-500", "opacity-1"],
278
+ true: ["opacity-1"],
262
279
  false: [],
263
280
  },
264
281
  },
265
282
  });
266
283
 
284
+ /**
285
+ * RowActions component displays actions as individual buttons or a dropdown menu
286
+ * based on the number of actions provided.
287
+ *
288
+ * - If there is a single action, it displays a standalone button.
289
+ * - If there are multiple actions, they are grouped into a dropdown menu.
290
+ *
291
+ * @param {RowActionsProps} props - The properties for the component.
292
+ * @param {Action[]} props.actions - A list of actions to display, including their labels, icons, and handlers.
293
+ * @returns {JSX.Element} A React component rendering the actions.
294
+ */
295
+ const RowActions = ({ actions }) => {
296
+ const selectedActions = actions.filter(action => action.isSelected);
297
+ const normalActions = actions.filter(action => (action.isVisible ?? true) && action.variant !== "danger");
298
+ const dangerActions = actions.filter(action => (action.isVisible ?? true) && action.variant === "danger");
299
+ if (actions.length === 0) {
300
+ return null;
301
+ }
302
+ if (actions.length === 1) {
303
+ const [action] = actions;
304
+ return (action &&
305
+ (action.isVisible ?? true) && (jsxRuntime.jsx(reactComponents.Button, { disabled: action.disabled || action.loading, onClick: event => {
306
+ event.stopPropagation();
307
+ action.onClick();
308
+ }, prefix: jsxRuntime.jsx(ActionIcon, { action: action }), size: "small", variant: "secondary", children: action.label })));
309
+ }
310
+ return (jsxRuntime.jsxs("div", { className: "flex items-center", "data-testid": "row-actions", children: [selectedActions.map((action, index) => (jsxRuntime.jsx(reactComponents.IconButton, { className: "mr-2", icon: action.iconName ? jsxRuntime.jsx(reactComponents.Icon, { name: action.iconName, size: "small" }) : undefined, onClick: event => {
311
+ event.stopPropagation();
312
+ action.onClick();
313
+ }, size: "small", variant: "ghost-neutral" }, `selected-action-${index}`))), jsxRuntime.jsx(reactComponents.MoreMenu, { customButton: jsxRuntime.jsx(reactComponents.IconButton, { icon: jsxRuntime.jsx(reactComponents.Icon, { name: "EllipsisHorizontal", size: "medium" }), onClick: event => event.stopPropagation(), size: "small", variant: "ghost-neutral" }), children: close => (jsxRuntime.jsxs(reactComponents.MenuList, { children: [normalActions.map((action, index) => (jsxRuntime.jsx(reactComponents.MenuItem, { disabled: action.disabled || action.loading, label: action.label, onClick: () => {
314
+ action.onClick();
315
+ close();
316
+ }, prefix: jsxRuntime.jsx(ActionIcon, { action: action }), variant: action.variant }, `action-${index}`))), normalActions.length > 0 && dangerActions.length > 0 && jsxRuntime.jsx(reactComponents.MenuDivider, {}), dangerActions.map((action, index) => (jsxRuntime.jsx(reactComponents.MenuItem, { disabled: action.disabled || action.loading, label: action.label, onClick: () => {
317
+ action.onClick();
318
+ close();
319
+ }, prefix: jsxRuntime.jsx(ActionIcon, { action: action }), variant: action.variant }, `danger-action-${index}`)))] })) })] }));
320
+ };
321
+ const ActionIcon = ({ action }) => {
322
+ if (action.loading) {
323
+ return jsxRuntime.jsx(reactComponents.Spinner, { size: "small" });
324
+ }
325
+ if (action.iconName) {
326
+ return jsxRuntime.jsx(reactComponents.Icon, { name: action.iconName, size: "medium" });
327
+ }
328
+ return null;
329
+ };
330
+
267
331
  /**
268
332
  * The SortIndicator is used in the table header to indicate the current sort order of the column.
269
333
  * This is a visual only component and does not handle the sorting logic.
@@ -278,24 +342,23 @@ const cvaResizeHandel = cssClassVarianceUtilities.cvaMerge(["absolute", "cursor-
278
342
  * @returns {ReactElement} A React element representing the sorting indicator.
279
343
  */
280
344
  const SortIndicator = ({ sortingState = false, dataTestId, className, ...rest }) => {
281
- return (jsxRuntime.jsx("span", { className: cvaSortIndicator({ sortingState, className }), "data-testid": dataTestId, role: "separator", ...rest }));
345
+ return (jsxRuntime.jsx("div", { className: cvaSortIndicator({ sortingState, className }), "data-testid": dataTestId, role: "separator", ...rest }));
282
346
  };
283
347
  const cvaSortIndicator = cssClassVarianceUtilities.cvaMerge([
284
- "relative",
285
348
  "before:block",
286
- "before:h-0",
349
+ "before:h-1",
287
350
  "before:absolute",
288
- "before:w-0",
289
- "before:bottom-0.5",
351
+ "before:top-[13px]",
352
+ "before:w-1",
290
353
  "before:content-['_']",
291
354
  "before:border-4",
292
355
  "before:border-solid",
293
356
  "before:border-transparent",
294
357
  "after:block",
295
- "after:h-0",
358
+ "after:h-1",
296
359
  "after:absolute",
297
- "after:w-0",
298
- "after:-bottom-[9px]",
360
+ "after:w-1",
361
+ "after:top-[18px]",
299
362
  "after:content-['_']",
300
363
  "after:border-4",
301
364
  "after:border-solid",
@@ -305,9 +368,9 @@ const cvaSortIndicator = cssClassVarianceUtilities.cvaMerge([
305
368
  ], {
306
369
  variants: {
307
370
  sortingState: {
308
- asc: ["before:border-b-primary-600"],
309
- desc: ["after:border-t-primary-600"],
310
- false: "",
371
+ asc: ["before:border-b-neutral-400 after:hidden"],
372
+ desc: ["before:hidden after:border-t-neutral-400"],
373
+ false: "hidden",
311
374
  },
312
375
  },
313
376
  });
@@ -325,7 +388,7 @@ const cvaSortIndicator = cssClassVarianceUtilities.cvaMerge([
325
388
  const TableRoot = ({ dataTestId, className, children, ...rest }) => {
326
389
  return (jsxRuntime.jsx("table", { className: cvaTableRoot({ className }), "data-testid": dataTestId, ...rest, children: children }));
327
390
  };
328
- const cvaTableRoot = cssClassVarianceUtilities.cvaMerge(["border-spacing-0", "min-w-full", "grid-rows-min-fr", "grid"]);
391
+ const cvaTableRoot = cssClassVarianceUtilities.cvaMerge(["border-spacing-0", "min-w-full", "border-collapse", "grid-rows-min-fr", "grid"]);
329
392
 
330
393
  const cvaTagsCell = cssClassVarianceUtilities.cvaMerge(["flex", "gap-2"]);
331
394
 
@@ -366,9 +429,22 @@ const Tbody = ({ dataTestId, className, children, ...rest }) => {
366
429
  * @returns {ReactElement} Td component
367
430
  */
368
431
  const Td = ({ dataTestId, className, children, ...rest }) => {
369
- return (jsxRuntime.jsx("td", { className: cvaTd({ className }), "data-testid": dataTestId, ...rest, children: children }));
432
+ return (jsxRuntime.jsx("td", { className: cvaTd({ className }), "data-testid": dataTestId, ...rest, children: Array.isArray(children)
433
+ ? children
434
+ .filter(Boolean)
435
+ .map((child, index) => (react.isValidElement(child) ? react.cloneElement(child, { key: index }) : child))
436
+ : children }));
370
437
  };
371
- const cvaTd = cssClassVarianceUtilities.cvaMerge(["overflow-hidden", "align-middle", "py-table-spacing", "px-2", "whitespace-nowrap"]);
438
+ const cvaTd = cssClassVarianceUtilities.cvaMerge([
439
+ "overflow-hidden",
440
+ "border-r",
441
+ "border-neutral-200",
442
+ "align-middle",
443
+ "text-ellipsis",
444
+ "whitespace-nowrap",
445
+ "py-table-spacing",
446
+ "px-4",
447
+ ]);
372
448
 
373
449
  const cvaTextCell = cssClassVarianceUtilities.cvaMerge(["truncate", "text-sm"]);
374
450
  const cvaTextCellTooltip = cssClassVarianceUtilities.cvaMerge(["grid"]);
@@ -412,25 +488,36 @@ const Tfoot = ({ dataTestId, className, children, ...rest }) => {
412
488
  * @param {ThProps} props - The props for the Th component
413
489
  * @returns {ReactElement} Th component
414
490
  */
415
- const Th = ({ dataTestId, className, children, tooltipLabel, ...rest }) => {
416
- const [showToolTip, setShowTooltipVisible] = react.useState(false);
417
- const [thWidth, setThWidth] = react.useState(0);
418
- const [childrenWidth, setChildrenWidth] = react.useState(0);
419
- react.useEffect(() => {
420
- setShowTooltipVisible(thWidth > childrenWidth);
421
- }, [thWidth, childrenWidth, tooltipLabel]);
422
- return (jsxRuntime.jsx("th", { className: cvaTh({ className }), "data-testid": dataTestId, ref: elementRef => setThWidth(elementRef ? elementRef.clientWidth : 0), ...rest, children: tooltipLabel ? (jsxRuntime.jsx(reactComponents.Tooltip, { className: cvaTooltip(), disabled: showToolTip, label: tooltipLabel, placement: "bottom", children: jsxRuntime.jsx("div", { className: cvaChildrenContainer(), ref: elementRef => setChildrenWidth(elementRef ? elementRef.clientWidth : 0), children: children }) })) : (jsxRuntime.jsx("div", { className: cvaChildrenContainer(), ref: elementRef => setChildrenWidth(elementRef ? elementRef.clientWidth : 0), children: children })) }));
491
+ const Th = ({ dataTestId, className, children, ...rest }) => {
492
+ return (jsxRuntime.jsx("th", { className: cvaTh({ className }), "data-testid": dataTestId, ...rest, children: jsxRuntime.jsx("div", { className: cvaChildrenContainer(), children: Array.isArray(children)
493
+ ? children
494
+ .filter(Boolean)
495
+ .map((child, index) => (react.isValidElement(child) ? react.cloneElement(child, { key: index }) : child))
496
+ : children }) }));
423
497
  };
424
498
  const cvaTh = cssClassVarianceUtilities.cvaMerge([
425
499
  "hover:bg-neutral-200",
426
500
  "text-sm",
501
+ "border-r",
502
+ "border-neutral-200",
427
503
  "overflow-hidden",
428
504
  "font-medium",
429
505
  "whitespace-nowrap",
506
+ "h-[46px]",
430
507
  "p-0",
431
508
  ]);
432
- const cvaTooltip = cssClassVarianceUtilities.cvaMerge(["w-full"]);
433
- const cvaChildrenContainer = cssClassVarianceUtilities.cvaMerge(["max-w-full", "px-3", "py-table-spacing"]);
509
+ const cvaChildrenContainer = cssClassVarianceUtilities.cvaMerge([
510
+ "w-full",
511
+ "min-w-0",
512
+ "flex",
513
+ "items-center",
514
+ "overflow-hidden",
515
+ "text-ellipsis",
516
+ "whitespace-nowrap",
517
+ "pl-4",
518
+ "py-table-spacing",
519
+ "pr-2",
520
+ ]);
434
521
 
435
522
  /**
436
523
  * The Thead is a wrapper for the thead html element.
@@ -470,7 +557,7 @@ const cvaThead = cssClassVarianceUtilities.cvaMerge(["bg-neutral-100", "text-lef
470
557
  const Tr = ({ dataTestId, className, children, layout = "default", ...rest }) => {
471
558
  return (jsxRuntime.jsx("tr", { className: cvaTr({ layout, className }), "data-testid": dataTestId, role: "row", ...rest, children: children }));
472
559
  };
473
- const cvaTr = cssClassVarianceUtilities.cvaMerge(["border-b", "border-neutral-300", "w-full", "h-max"], {
560
+ const cvaTr = cssClassVarianceUtilities.cvaMerge(["w-full", "h-max", "border-b", "border-neutral-200"], {
474
561
  variants: {
475
562
  layout: {
476
563
  default: "",
@@ -495,6 +582,7 @@ exports.NoticeCell = NoticeCell;
495
582
  exports.NumberCell = NumberCell;
496
583
  exports.PlainDateCell = PlainDateCell;
497
584
  exports.ResizeHandle = ResizeHandle;
585
+ exports.RowActions = RowActions;
498
586
  exports.SortIndicator = SortIndicator;
499
587
  exports.TableRoot = TableRoot;
500
588
  exports.TagsCell = TagsCell;
package/index.esm.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { Button, Icon, Heading, Indicator, ExternalLink, Tooltip, Tag, Notice } from '@trackunit/react-components';
2
+ import { Button, Icon, Heading, Indicator, ExternalLink, Tooltip, Tag, Notice, IconButton, MoreMenu, MenuList, MenuItem, MenuDivider, Spinner } from '@trackunit/react-components';
3
3
  import { Checkbox } from '@trackunit/react-form-components';
4
4
  import { cvaMerge } from '@trackunit/css-class-variance-utilities';
5
5
  import { formatDateUtil, timeSinceAuto, timeSinceInDays } from '@trackunit/date-and-time-utils';
6
- import { Fragment, useState, useEffect } from 'react';
6
+ import { Fragment, useState, isValidElement, cloneElement } from 'react';
7
7
  import { Temporal } from '@js-temporal/polyfill';
8
8
  import { DateTimeFormat } from '@trackunit/shared-utils';
9
9
 
@@ -72,7 +72,7 @@ const cvaIdentityCell = cvaMerge(["grid", "items-center", "text-sm"], {
72
72
  density: "default",
73
73
  },
74
74
  });
75
- const cvaIdentityCellLayout = cvaMerge(["grid", "items-center", "gap-1", "text-sm"], {
75
+ const cvaIdentityCellLayout = cvaMerge(["grid", "items-center", "text-sm"], {
76
76
  variants: {
77
77
  density: {
78
78
  default: "grid-rows-[min-content_auto]",
@@ -83,7 +83,7 @@ const cvaIdentityCellLayout = cvaMerge(["grid", "items-center", "gap-1", "text-s
83
83
  density: "default",
84
84
  },
85
85
  });
86
- const cvaIdentityCellDetails = cvaMerge(["flex", "w-full", "min-w-0", "items-center", "truncate", "text-xs", "text-gray-500"], {
86
+ const cvaIdentityCellDetails = cvaMerge(["flex", "w-full", "min-w-0", "items-center", "truncate", "text-xs", "text-gray-600", "font-medium"], {
87
87
  variants: {
88
88
  density: {
89
89
  default: "pt-0",
@@ -100,7 +100,7 @@ const cvaIdentityCellDetailsItem = cvaMerge(["last:truncate"]);
100
100
  * @returns {ReactElement} LinkCell component
101
101
  */
102
102
  const IdentityCell = ({ className, dataTestId = "identity-cell", density = "default", title, details = [], thumbnail, }) => {
103
- return (jsxs("div", { className: cvaIdentityCell({ className, density, hasThumbnail: !!thumbnail }), "data-testid": dataTestId, children: [thumbnail ? (jsx("div", { className: "mr-1 flex h-8 w-8 items-center justify-center overflow-hidden rounded-md", children: thumbnail })) : null, jsxs("div", { className: cvaIdentityCellLayout({ className, density }), children: [jsx("div", { className: "flex w-full min-w-0 truncate text-sm", children: jsx(Heading, { className: "truncate text-sm", dataTestId: `${dataTestId}-name`, variant: "tertiary", children: title }) }), details.length > 0 && (jsx("div", { className: cvaIdentityCellDetails({ className, density }), children: details.map((value, index, array) => (jsxs(Fragment, { children: [jsx("span", { className: cvaIdentityCellDetailsItem({ className }), children: value }), index < array.length - 1 && (jsx("div", { className: "mx-0.5 flex items-center", children: jsx(Icon, { className: "w-4 text-neutral-200", color: "neutral", name: "Slash", size: "small" }) }))] }, index))) }))] })] }));
103
+ return (jsxs("div", { className: cvaIdentityCell({ className, density, hasThumbnail: !!thumbnail }), "data-testid": dataTestId, children: [thumbnail ? (jsx("div", { className: "mr-1 flex h-8 w-8 items-center justify-center overflow-hidden rounded-md", children: thumbnail })) : null, jsxs("div", { className: cvaIdentityCellLayout({ className, density }), children: [jsx("div", { className: "flex w-full min-w-0 truncate text-sm", children: jsx(Heading, { className: "truncate text-sm", dataTestId: `${dataTestId}-name`, variant: "tertiary", children: title }) }), details.length > 0 && (jsx("div", { className: cvaIdentityCellDetails({ className, density }), children: details.map((value, index, array) => (jsxs(Fragment, { children: [jsx("span", { className: cvaIdentityCellDetailsItem({ className }), children: value }), index < array.length - 1 && (jsx("div", { className: "mx-0.5 flex items-center", children: jsx(Icon, { className: "w-4 text-neutral-300", color: "neutral", name: "Slash", size: "small" }) }))] }, index))) }))] })] }));
104
104
  };
105
105
 
106
106
  /**
@@ -253,15 +253,79 @@ const ResizeHandle = ({ isResizing = false, dataTestId, onMouseDown: onMouseDown
253
253
  };
254
254
  return (jsx("div", { className: cvaResizeHandel({ isResizing, className }), "data-testid": dataTestId, onMouseDown: event => onMouseDown(event), role: "separator", ...rest }));
255
255
  };
256
- const cvaResizeHandel = cvaMerge(["absolute", "cursor-col-resize", "right-0", "top-0", "h-full", "w-1", "select-none", "hover:bg-primary-400"], {
256
+ const cvaResizeHandel = cvaMerge([
257
+ "absolute",
258
+ "cursor-ew-resize",
259
+ "z-2",
260
+ "h-full",
261
+ "w-2",
262
+ "top-0",
263
+ "right-[-3px]",
264
+ "flex",
265
+ "items-center",
266
+ "after:block",
267
+ "after:left-[calc(50%-2px)]",
268
+ "after:h-full",
269
+ "after:z-[1]",
270
+ "after:absolute",
271
+ "after:w-0.5",
272
+ "after:hover:bg-info-500",
273
+ ], {
257
274
  variants: {
258
275
  isResizing: {
259
- true: ["bg-primary-500", "opacity-1"],
276
+ true: ["opacity-1"],
260
277
  false: [],
261
278
  },
262
279
  },
263
280
  });
264
281
 
282
+ /**
283
+ * RowActions component displays actions as individual buttons or a dropdown menu
284
+ * based on the number of actions provided.
285
+ *
286
+ * - If there is a single action, it displays a standalone button.
287
+ * - If there are multiple actions, they are grouped into a dropdown menu.
288
+ *
289
+ * @param {RowActionsProps} props - The properties for the component.
290
+ * @param {Action[]} props.actions - A list of actions to display, including their labels, icons, and handlers.
291
+ * @returns {JSX.Element} A React component rendering the actions.
292
+ */
293
+ const RowActions = ({ actions }) => {
294
+ const selectedActions = actions.filter(action => action.isSelected);
295
+ const normalActions = actions.filter(action => (action.isVisible ?? true) && action.variant !== "danger");
296
+ const dangerActions = actions.filter(action => (action.isVisible ?? true) && action.variant === "danger");
297
+ if (actions.length === 0) {
298
+ return null;
299
+ }
300
+ if (actions.length === 1) {
301
+ const [action] = actions;
302
+ return (action &&
303
+ (action.isVisible ?? true) && (jsx(Button, { disabled: action.disabled || action.loading, onClick: event => {
304
+ event.stopPropagation();
305
+ action.onClick();
306
+ }, prefix: jsx(ActionIcon, { action: action }), size: "small", variant: "secondary", children: action.label })));
307
+ }
308
+ return (jsxs("div", { className: "flex items-center", "data-testid": "row-actions", children: [selectedActions.map((action, index) => (jsx(IconButton, { className: "mr-2", icon: action.iconName ? jsx(Icon, { name: action.iconName, size: "small" }) : undefined, onClick: event => {
309
+ event.stopPropagation();
310
+ action.onClick();
311
+ }, size: "small", variant: "ghost-neutral" }, `selected-action-${index}`))), jsx(MoreMenu, { customButton: jsx(IconButton, { icon: jsx(Icon, { name: "EllipsisHorizontal", size: "medium" }), onClick: event => event.stopPropagation(), size: "small", variant: "ghost-neutral" }), children: close => (jsxs(MenuList, { children: [normalActions.map((action, index) => (jsx(MenuItem, { disabled: action.disabled || action.loading, label: action.label, onClick: () => {
312
+ action.onClick();
313
+ close();
314
+ }, prefix: jsx(ActionIcon, { action: action }), variant: action.variant }, `action-${index}`))), normalActions.length > 0 && dangerActions.length > 0 && jsx(MenuDivider, {}), dangerActions.map((action, index) => (jsx(MenuItem, { disabled: action.disabled || action.loading, label: action.label, onClick: () => {
315
+ action.onClick();
316
+ close();
317
+ }, prefix: jsx(ActionIcon, { action: action }), variant: action.variant }, `danger-action-${index}`)))] })) })] }));
318
+ };
319
+ const ActionIcon = ({ action }) => {
320
+ if (action.loading) {
321
+ return jsx(Spinner, { size: "small" });
322
+ }
323
+ if (action.iconName) {
324
+ return jsx(Icon, { name: action.iconName, size: "medium" });
325
+ }
326
+ return null;
327
+ };
328
+
265
329
  /**
266
330
  * The SortIndicator is used in the table header to indicate the current sort order of the column.
267
331
  * This is a visual only component and does not handle the sorting logic.
@@ -276,24 +340,23 @@ const cvaResizeHandel = cvaMerge(["absolute", "cursor-col-resize", "right-0", "t
276
340
  * @returns {ReactElement} A React element representing the sorting indicator.
277
341
  */
278
342
  const SortIndicator = ({ sortingState = false, dataTestId, className, ...rest }) => {
279
- return (jsx("span", { className: cvaSortIndicator({ sortingState, className }), "data-testid": dataTestId, role: "separator", ...rest }));
343
+ return (jsx("div", { className: cvaSortIndicator({ sortingState, className }), "data-testid": dataTestId, role: "separator", ...rest }));
280
344
  };
281
345
  const cvaSortIndicator = cvaMerge([
282
- "relative",
283
346
  "before:block",
284
- "before:h-0",
347
+ "before:h-1",
285
348
  "before:absolute",
286
- "before:w-0",
287
- "before:bottom-0.5",
349
+ "before:top-[13px]",
350
+ "before:w-1",
288
351
  "before:content-['_']",
289
352
  "before:border-4",
290
353
  "before:border-solid",
291
354
  "before:border-transparent",
292
355
  "after:block",
293
- "after:h-0",
356
+ "after:h-1",
294
357
  "after:absolute",
295
- "after:w-0",
296
- "after:-bottom-[9px]",
358
+ "after:w-1",
359
+ "after:top-[18px]",
297
360
  "after:content-['_']",
298
361
  "after:border-4",
299
362
  "after:border-solid",
@@ -303,9 +366,9 @@ const cvaSortIndicator = cvaMerge([
303
366
  ], {
304
367
  variants: {
305
368
  sortingState: {
306
- asc: ["before:border-b-primary-600"],
307
- desc: ["after:border-t-primary-600"],
308
- false: "",
369
+ asc: ["before:border-b-neutral-400 after:hidden"],
370
+ desc: ["before:hidden after:border-t-neutral-400"],
371
+ false: "hidden",
309
372
  },
310
373
  },
311
374
  });
@@ -323,7 +386,7 @@ const cvaSortIndicator = cvaMerge([
323
386
  const TableRoot = ({ dataTestId, className, children, ...rest }) => {
324
387
  return (jsx("table", { className: cvaTableRoot({ className }), "data-testid": dataTestId, ...rest, children: children }));
325
388
  };
326
- const cvaTableRoot = cvaMerge(["border-spacing-0", "min-w-full", "grid-rows-min-fr", "grid"]);
389
+ const cvaTableRoot = cvaMerge(["border-spacing-0", "min-w-full", "border-collapse", "grid-rows-min-fr", "grid"]);
327
390
 
328
391
  const cvaTagsCell = cvaMerge(["flex", "gap-2"]);
329
392
 
@@ -364,9 +427,22 @@ const Tbody = ({ dataTestId, className, children, ...rest }) => {
364
427
  * @returns {ReactElement} Td component
365
428
  */
366
429
  const Td = ({ dataTestId, className, children, ...rest }) => {
367
- return (jsx("td", { className: cvaTd({ className }), "data-testid": dataTestId, ...rest, children: children }));
430
+ return (jsx("td", { className: cvaTd({ className }), "data-testid": dataTestId, ...rest, children: Array.isArray(children)
431
+ ? children
432
+ .filter(Boolean)
433
+ .map((child, index) => (isValidElement(child) ? cloneElement(child, { key: index }) : child))
434
+ : children }));
368
435
  };
369
- const cvaTd = cvaMerge(["overflow-hidden", "align-middle", "py-table-spacing", "px-2", "whitespace-nowrap"]);
436
+ const cvaTd = cvaMerge([
437
+ "overflow-hidden",
438
+ "border-r",
439
+ "border-neutral-200",
440
+ "align-middle",
441
+ "text-ellipsis",
442
+ "whitespace-nowrap",
443
+ "py-table-spacing",
444
+ "px-4",
445
+ ]);
370
446
 
371
447
  const cvaTextCell = cvaMerge(["truncate", "text-sm"]);
372
448
  const cvaTextCellTooltip = cvaMerge(["grid"]);
@@ -410,25 +486,36 @@ const Tfoot = ({ dataTestId, className, children, ...rest }) => {
410
486
  * @param {ThProps} props - The props for the Th component
411
487
  * @returns {ReactElement} Th component
412
488
  */
413
- const Th = ({ dataTestId, className, children, tooltipLabel, ...rest }) => {
414
- const [showToolTip, setShowTooltipVisible] = useState(false);
415
- const [thWidth, setThWidth] = useState(0);
416
- const [childrenWidth, setChildrenWidth] = useState(0);
417
- useEffect(() => {
418
- setShowTooltipVisible(thWidth > childrenWidth);
419
- }, [thWidth, childrenWidth, tooltipLabel]);
420
- return (jsx("th", { className: cvaTh({ className }), "data-testid": dataTestId, ref: elementRef => setThWidth(elementRef ? elementRef.clientWidth : 0), ...rest, children: tooltipLabel ? (jsx(Tooltip, { className: cvaTooltip(), disabled: showToolTip, label: tooltipLabel, placement: "bottom", children: jsx("div", { className: cvaChildrenContainer(), ref: elementRef => setChildrenWidth(elementRef ? elementRef.clientWidth : 0), children: children }) })) : (jsx("div", { className: cvaChildrenContainer(), ref: elementRef => setChildrenWidth(elementRef ? elementRef.clientWidth : 0), children: children })) }));
489
+ const Th = ({ dataTestId, className, children, ...rest }) => {
490
+ return (jsx("th", { className: cvaTh({ className }), "data-testid": dataTestId, ...rest, children: jsx("div", { className: cvaChildrenContainer(), children: Array.isArray(children)
491
+ ? children
492
+ .filter(Boolean)
493
+ .map((child, index) => (isValidElement(child) ? cloneElement(child, { key: index }) : child))
494
+ : children }) }));
421
495
  };
422
496
  const cvaTh = cvaMerge([
423
497
  "hover:bg-neutral-200",
424
498
  "text-sm",
499
+ "border-r",
500
+ "border-neutral-200",
425
501
  "overflow-hidden",
426
502
  "font-medium",
427
503
  "whitespace-nowrap",
504
+ "h-[46px]",
428
505
  "p-0",
429
506
  ]);
430
- const cvaTooltip = cvaMerge(["w-full"]);
431
- const cvaChildrenContainer = cvaMerge(["max-w-full", "px-3", "py-table-spacing"]);
507
+ const cvaChildrenContainer = cvaMerge([
508
+ "w-full",
509
+ "min-w-0",
510
+ "flex",
511
+ "items-center",
512
+ "overflow-hidden",
513
+ "text-ellipsis",
514
+ "whitespace-nowrap",
515
+ "pl-4",
516
+ "py-table-spacing",
517
+ "pr-2",
518
+ ]);
432
519
 
433
520
  /**
434
521
  * The Thead is a wrapper for the thead html element.
@@ -468,7 +555,7 @@ const cvaThead = cvaMerge(["bg-neutral-100", "text-left"], {
468
555
  const Tr = ({ dataTestId, className, children, layout = "default", ...rest }) => {
469
556
  return (jsx("tr", { className: cvaTr({ layout, className }), "data-testid": dataTestId, role: "row", ...rest, children: children }));
470
557
  };
471
- const cvaTr = cvaMerge(["border-b", "border-neutral-300", "w-full", "h-max"], {
558
+ const cvaTr = cvaMerge(["w-full", "h-max", "border-b", "border-neutral-200"], {
472
559
  variants: {
473
560
  layout: {
474
561
  default: "",
@@ -480,4 +567,4 @@ const cvaTr = cvaMerge(["border-b", "border-neutral-300", "w-full", "h-max"], {
480
567
  },
481
568
  });
482
569
 
483
- export { ButtonCell, CheckboxCell, DateTimeCell, IdentityCell, ImageCell, IndicatorCell, LinkCell, MultiRowTableCell, MultiValueTextCell, NoticeCell, NumberCell, PlainDateCell, ResizeHandle, SortIndicator, TableRoot, TagsCell, Tbody, Td, TextCell, Tfoot, Th, Thead, Tr };
570
+ export { ButtonCell, CheckboxCell, DateTimeCell, IdentityCell, ImageCell, IndicatorCell, LinkCell, MultiRowTableCell, MultiValueTextCell, NoticeCell, NumberCell, PlainDateCell, ResizeHandle, RowActions, SortIndicator, TableRoot, TagsCell, Tbody, Td, TextCell, Tfoot, Th, Thead, Tr };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-table-base-components",
3
- "version": "1.3.131",
3
+ "version": "1.3.134",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -9,12 +9,12 @@
9
9
  "dependencies": {
10
10
  "react": "19.0.0",
11
11
  "@js-temporal/polyfill": "^0.4.4",
12
- "@trackunit/react-components": "1.4.115",
13
- "@trackunit/ui-icons": "1.3.97",
14
- "@trackunit/react-form-components": "1.3.131",
15
- "@trackunit/css-class-variance-utilities": "1.3.97",
16
- "@trackunit/date-and-time-utils": "1.3.97",
17
- "@trackunit/shared-utils": "1.5.97"
12
+ "@trackunit/react-components": "1.4.118",
13
+ "@trackunit/ui-icons": "1.3.100",
14
+ "@trackunit/react-form-components": "1.3.134",
15
+ "@trackunit/css-class-variance-utilities": "1.3.98",
16
+ "@trackunit/date-and-time-utils": "1.3.98",
17
+ "@trackunit/shared-utils": "1.5.98"
18
18
  },
19
19
  "module": "./index.esm.js",
20
20
  "main": "./index.cjs.js",
@@ -0,0 +1,28 @@
1
+ import { MenuItemVariant } from "@trackunit/react-components";
2
+ import { IconName } from "@trackunit/ui-icons";
3
+ export type Action = {
4
+ label: string;
5
+ variant?: MenuItemVariant;
6
+ onClick: () => void;
7
+ iconName?: IconName;
8
+ isSelected?: boolean;
9
+ disabled?: boolean;
10
+ loading?: boolean;
11
+ isVisible?: boolean | null;
12
+ };
13
+ interface RowActionsProps {
14
+ actions: Action[];
15
+ }
16
+ /**
17
+ * RowActions component displays actions as individual buttons or a dropdown menu
18
+ * based on the number of actions provided.
19
+ *
20
+ * - If there is a single action, it displays a standalone button.
21
+ * - If there are multiple actions, they are grouped into a dropdown menu.
22
+ *
23
+ * @param {RowActionsProps} props - The properties for the component.
24
+ * @param {Action[]} props.actions - A list of actions to display, including their labels, icons, and handlers.
25
+ * @returns {JSX.Element} A React component rendering the actions.
26
+ */
27
+ export declare const RowActions: ({ actions }: RowActionsProps) => false | import("react/jsx-runtime").JSX.Element | null | undefined;
28
+ export {};
@@ -0,0 +1,9 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { RowActions } from "./RowActions";
3
+ declare const meta: Meta<typeof RowActions>;
4
+ export default meta;
5
+ export declare const packageName: () => import("react/jsx-runtime").JSX.Element;
6
+ export declare const SingleAction: StoryObj<typeof RowActions>;
7
+ export declare const MultipleActions: StoryObj<typeof RowActions>;
8
+ export declare const NoActions: StoryObj<typeof RowActions>;
9
+ export declare const OnlyDangerActions: StoryObj<typeof RowActions>;
@@ -13,10 +13,6 @@ export interface ThProps extends CommonProps, React.HTMLAttributes<HTMLTableCell
13
13
  * Children to render in the table cell.
14
14
  */
15
15
  children?: ReactNode;
16
- /**
17
- * Label for the tooltip.
18
- */
19
- tooltipLabel?: string;
20
16
  }
21
17
  /**
22
18
  * The Th is a wrapper for the th html element.
@@ -28,4 +24,4 @@ export interface ThProps extends CommonProps, React.HTMLAttributes<HTMLTableCell
28
24
  * @param {ThProps} props - The props for the Th component
29
25
  * @returns {ReactElement} Th component
30
26
  */
31
- export declare const Th: ({ dataTestId, className, children, tooltipLabel, ...rest }: ThProps) => import("react/jsx-runtime").JSX.Element;
27
+ export declare const Th: ({ dataTestId, className, children, ...rest }: ThProps) => import("react/jsx-runtime").JSX.Element;
package/src/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export * from "./components/NoticeCell/NoticeCell";
11
11
  export * from "./components/NumberCell/NumberCell";
12
12
  export * from "./components/PlainDateCell/PlainDateCell";
13
13
  export * from "./components/ResizeHandle";
14
+ export * from "./components/RowActions/RowActions";
14
15
  export * from "./components/SortIndicator";
15
16
  export * from "./components/TableRoot";
16
17
  export * from "./components/TagsCell/TagsCell";