@trackunit/react-table-base-components 2.7.22-alpha-15da733192f.0 → 2.8.1

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
@@ -12,8 +12,6 @@ var react = require('react');
12
12
  var sharedUtils = require('@trackunit/shared-utils');
13
13
 
14
14
  var defaultTranslations = {
15
- "copyableCell.copied": "Copied!",
16
- "copyableCell.copy": "Copy",
17
15
  "rowActions.moreActions": "More actions"
18
16
  };
19
17
 
@@ -153,85 +151,6 @@ const CheckboxCell = ({ checked, className, "data-testid": dataTestId, style })
153
151
  return (jsxRuntime.jsx("div", { className: cvaCheckboxCell({ className }), "data-testid": dataTestId, style: style, children: jsxRuntime.jsx(reactFormComponents.Checkbox, { checked: checked, readOnly: true }) }));
154
152
  };
155
153
 
156
- const cvaMultiRowTableCellSection = cssClassVarianceUtilities.cva({
157
- base: ["flex", "flex-col", "justify-center", "min-w-0", "max-w-full", "overflow-hidden"],
158
- });
159
- const cvaMultiRowTableCell = cssClassVarianceUtilities.cva({ base: ["flex", "justify-start", "min-w-0", "max-w-full"] });
160
- const cvaMainRowText = cssClassVarianceUtilities.cva({ base: ["overflow-hidden", "text-ellipsis", "text-sm"] });
161
- const cvaSecondaryRowText = cssClassVarianceUtilities.cva({
162
- base: ["overflow-hidden", "text-ellipsis", "text-neutral-500", "text-xs"],
163
- });
164
-
165
- /**
166
- * MultiRowTableCell renders two stacked rows inside a single table cell — a primary `main` row
167
- * and a secondary row below it. String values are automatically styled with appropriate text sizes.
168
- *
169
- * ### When to use
170
- * Use MultiRowTableCell when a table cell needs to display a primary value with secondary metadata below —
171
- * for example, an asset name with its serial number, or a user name with their email.
172
- *
173
- * ### When not to use
174
- * Do not use MultiRowTableCell for single-line text — use `TextCell`.
175
- *
176
- * @example Name with description in a table cell
177
- * ```tsx
178
- * import { MultiRowTableCell } from "@trackunit/react-table-base-components";
179
- *
180
- * const cell = ({ row }) => (
181
- * <MultiRowTableCell main={row.original.name} secondary={row.original.serialNumber} />
182
- * );
183
- * ```
184
- * @param {MultiRowTableCellProps} props - The props for the MultiRowTableCell component
185
- * @returns {ReactElement} MultiRowTableCell component
186
- */
187
- const MultiRowTableCell = ({ main, secondary, "data-testid": dataTestId, className, style, }) => {
188
- return (jsxRuntime.jsx("div", { className: cvaMultiRowTableCell({ className }), style: style, children: jsxRuntime.jsxs("section", { className: cvaMultiRowTableCellSection(), "data-testid": dataTestId, children: [typeof main === "string" ? jsxRuntime.jsx("div", { className: cvaMainRowText(), children: main }) : main, typeof secondary === "string" && secondary !== "" ? (jsxRuntime.jsx("div", { className: cvaSecondaryRowText(), children: secondary })) : (secondary)] }) }));
189
- };
190
-
191
- const cvaCopyableCell = cssClassVarianceUtilities.cva({ base: ["overflow-hidden", "w-fit"] });
192
-
193
- /**
194
- * CopyableCell renders a table cell with click-to-copy functionality.
195
- * Supports single-line and multi-line layouts.
196
- *
197
- * ### When to use
198
- * Use CopyableCell inside table column definitions when a cell value (ID, name, reference) should be copyable.
199
- *
200
- * ### When not to use
201
- * For standalone copyable text outside of tables, use `CopyableText` from `@trackunit/react-components`.
202
- *
203
- * @example Single-line copyable cell
204
- * ```tsx
205
- * import { CopyableCell } from "@trackunit/react-table-base-components";
206
- *
207
- * const cell = ({ getValue }) => (
208
- * <CopyableCell text={getValue()} />
209
- * );
210
- * ```
211
- * @example Multi-line copyable cell
212
- * ```tsx
213
- * import { CopyableCell } from "@trackunit/react-table-base-components";
214
- *
215
- * const cell = ({ row }) => (
216
- * <CopyableCell
217
- * text={String(row.original.customerId)}
218
- * secondary={{ text: row.original.name }}
219
- * />
220
- * );
221
- * ```
222
- * @param {CopyableCellProps} props - The props for the CopyableCell component
223
- * @returns {ReactElement} CopyableCell component
224
- */
225
- const CopyableCell = ({ text, secondary, "data-testid": dataTestId, className, style, }) => {
226
- const [t] = useTranslation();
227
- const copyLabel = t("copyableCell.copy");
228
- const copiedLabel = t("copyableCell.copied");
229
- if (secondary !== undefined) {
230
- return (jsxRuntime.jsx(MultiRowTableCell, { className: cvaCopyableCell({ className }), "data-testid": dataTestId, main: jsxRuntime.jsx(reactComponents.CopyableText, { className: "self-start", copiedLabel: copiedLabel, copyLabel: copyLabel, text: text }), secondary: jsxRuntime.jsx(reactComponents.CopyableText, { className: "self-start", copiedLabel: copiedLabel, copyLabel: copyLabel, size: "xs", text: secondary.text }), style: style }));
231
- }
232
- return (jsxRuntime.jsx(reactComponents.CopyableText, { className: cvaCopyableCell({ className }), copiedLabel: copiedLabel, copyLabel: copyLabel, "data-testid": dataTestId, style: style, text: text }));
233
- };
234
-
235
154
  const cvaDateTimeCell = cssClassVarianceUtilities.cva({ base: [""] });
236
155
  const cvaDateTime$1 = cssClassVarianceUtilities.cva({ base: ["slashed-zero", "text-sm", "text-ellipsis"] });
237
156
  const cvaDateTimeSince$1 = cssClassVarianceUtilities.cva({ base: ["slashed-zero", "text-neutral-500", "text-xs", "text-ellipsis"] });
@@ -578,6 +497,41 @@ const LinkCell = ({ link, type, className, ref, ...rest }) => {
578
497
  return (jsxRuntime.jsx(reactComponents.ExternalLink, { className: cvaLinkCell({ className }), href: `${linkPrefix}${link}`, ref: ref, ...rest, color: "neutral", onClick: handleClick, children: link }));
579
498
  };
580
499
 
500
+ const cvaMultiRowTableCellSection = cssClassVarianceUtilities.cva({
501
+ base: ["flex", "flex-col", "justify-center", "min-w-0", "max-w-full", "overflow-hidden"],
502
+ });
503
+ const cvaMultiRowTableCell = cssClassVarianceUtilities.cva({ base: ["flex", "justify-start", "min-w-0", "max-w-full"] });
504
+ const cvaMainRowText = cssClassVarianceUtilities.cva({ base: ["overflow-hidden", "text-ellipsis", "text-sm"] });
505
+ const cvaSecondaryRowText = cssClassVarianceUtilities.cva({
506
+ base: ["overflow-hidden", "text-ellipsis", "text-neutral-500", "text-xs"],
507
+ });
508
+
509
+ /**
510
+ * MultiRowTableCell renders two stacked rows inside a single table cell — a primary `main` row
511
+ * and a secondary row below it. String values are automatically styled with appropriate text sizes.
512
+ *
513
+ * ### When to use
514
+ * Use MultiRowTableCell when a table cell needs to display a primary value with secondary metadata below —
515
+ * for example, an asset name with its serial number, or a user name with their email.
516
+ *
517
+ * ### When not to use
518
+ * Do not use MultiRowTableCell for single-line text — use `TextCell`.
519
+ *
520
+ * @example Name with description in a table cell
521
+ * ```tsx
522
+ * import { MultiRowTableCell } from "@trackunit/react-table-base-components";
523
+ *
524
+ * const cell = ({ row }) => (
525
+ * <MultiRowTableCell main={row.original.name} secondary={row.original.serialNumber} />
526
+ * );
527
+ * ```
528
+ * @param {MultiRowTableCellProps} props - The props for the MultiRowTableCell component
529
+ * @returns {ReactElement} MultiRowTableCell component
530
+ */
531
+ const MultiRowTableCell = ({ main, secondary, "data-testid": dataTestId, className, style, }) => {
532
+ return (jsxRuntime.jsx("div", { className: cvaMultiRowTableCell({ className }), style: style, children: jsxRuntime.jsxs("section", { className: cvaMultiRowTableCellSection(), "data-testid": dataTestId, children: [typeof main === "string" ? jsxRuntime.jsx("div", { className: cvaMainRowText(), children: main }) : main, typeof secondary === "string" && secondary !== "" ? (jsxRuntime.jsx("div", { className: cvaSecondaryRowText(), children: secondary })) : (secondary)] }) }));
533
+ };
534
+
581
535
  /**
582
536
  * Generic list to be used as tooltip content.
583
537
  * The component is data-shape agnostic. It supports primitives, React elements,
@@ -1367,7 +1321,6 @@ setupLibraryTranslations();
1367
1321
 
1368
1322
  exports.ButtonCell = ButtonCell;
1369
1323
  exports.CheckboxCell = CheckboxCell;
1370
- exports.CopyableCell = CopyableCell;
1371
1324
  exports.DateTimeCell = DateTimeCell;
1372
1325
  exports.HighlightCell = HighlightCell;
1373
1326
  exports.IdentityCell = IdentityCell;
package/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { registerTranslations, useNamespaceTranslation } from '@trackunit/i18n-library-translation';
3
- import { Button, Icon, CopyableText, Highlight, Heading, ExternalLink, DetailsList, Indicator, Spinner, Tooltip, Text, Tag, Notice, IconButton, MoreMenu, MenuContent, MenuItem, MenuDivider, useMeasure, useMergeRefs } from '@trackunit/react-components';
3
+ import { Button, Icon, Highlight, Heading, ExternalLink, DetailsList, Indicator, Spinner, Tooltip, Text, Tag, Notice, IconButton, MoreMenu, MenuContent, MenuItem, MenuDivider, useMeasure, useMergeRefs } from '@trackunit/react-components';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import { Checkbox } from '@trackunit/react-form-components';
6
6
  import { cva } from '@trackunit/css-class-variance-utilities';
@@ -10,8 +10,6 @@ import { useMemo, useState, useEffect, isValidElement, cloneElement } from 'reac
10
10
  import { DateTimeFormat } from '@trackunit/shared-utils';
11
11
 
12
12
  var defaultTranslations = {
13
- "copyableCell.copied": "Copied!",
14
- "copyableCell.copy": "Copy",
15
13
  "rowActions.moreActions": "More actions"
16
14
  };
17
15
 
@@ -151,85 +149,6 @@ const CheckboxCell = ({ checked, className, "data-testid": dataTestId, style })
151
149
  return (jsx("div", { className: cvaCheckboxCell({ className }), "data-testid": dataTestId, style: style, children: jsx(Checkbox, { checked: checked, readOnly: true }) }));
152
150
  };
153
151
 
154
- const cvaMultiRowTableCellSection = cva({
155
- base: ["flex", "flex-col", "justify-center", "min-w-0", "max-w-full", "overflow-hidden"],
156
- });
157
- const cvaMultiRowTableCell = cva({ base: ["flex", "justify-start", "min-w-0", "max-w-full"] });
158
- const cvaMainRowText = cva({ base: ["overflow-hidden", "text-ellipsis", "text-sm"] });
159
- const cvaSecondaryRowText = cva({
160
- base: ["overflow-hidden", "text-ellipsis", "text-neutral-500", "text-xs"],
161
- });
162
-
163
- /**
164
- * MultiRowTableCell renders two stacked rows inside a single table cell — a primary `main` row
165
- * and a secondary row below it. String values are automatically styled with appropriate text sizes.
166
- *
167
- * ### When to use
168
- * Use MultiRowTableCell when a table cell needs to display a primary value with secondary metadata below —
169
- * for example, an asset name with its serial number, or a user name with their email.
170
- *
171
- * ### When not to use
172
- * Do not use MultiRowTableCell for single-line text — use `TextCell`.
173
- *
174
- * @example Name with description in a table cell
175
- * ```tsx
176
- * import { MultiRowTableCell } from "@trackunit/react-table-base-components";
177
- *
178
- * const cell = ({ row }) => (
179
- * <MultiRowTableCell main={row.original.name} secondary={row.original.serialNumber} />
180
- * );
181
- * ```
182
- * @param {MultiRowTableCellProps} props - The props for the MultiRowTableCell component
183
- * @returns {ReactElement} MultiRowTableCell component
184
- */
185
- const MultiRowTableCell = ({ main, secondary, "data-testid": dataTestId, className, style, }) => {
186
- return (jsx("div", { className: cvaMultiRowTableCell({ className }), style: style, children: jsxs("section", { className: cvaMultiRowTableCellSection(), "data-testid": dataTestId, children: [typeof main === "string" ? jsx("div", { className: cvaMainRowText(), children: main }) : main, typeof secondary === "string" && secondary !== "" ? (jsx("div", { className: cvaSecondaryRowText(), children: secondary })) : (secondary)] }) }));
187
- };
188
-
189
- const cvaCopyableCell = cva({ base: ["overflow-hidden", "w-fit"] });
190
-
191
- /**
192
- * CopyableCell renders a table cell with click-to-copy functionality.
193
- * Supports single-line and multi-line layouts.
194
- *
195
- * ### When to use
196
- * Use CopyableCell inside table column definitions when a cell value (ID, name, reference) should be copyable.
197
- *
198
- * ### When not to use
199
- * For standalone copyable text outside of tables, use `CopyableText` from `@trackunit/react-components`.
200
- *
201
- * @example Single-line copyable cell
202
- * ```tsx
203
- * import { CopyableCell } from "@trackunit/react-table-base-components";
204
- *
205
- * const cell = ({ getValue }) => (
206
- * <CopyableCell text={getValue()} />
207
- * );
208
- * ```
209
- * @example Multi-line copyable cell
210
- * ```tsx
211
- * import { CopyableCell } from "@trackunit/react-table-base-components";
212
- *
213
- * const cell = ({ row }) => (
214
- * <CopyableCell
215
- * text={String(row.original.customerId)}
216
- * secondary={{ text: row.original.name }}
217
- * />
218
- * );
219
- * ```
220
- * @param {CopyableCellProps} props - The props for the CopyableCell component
221
- * @returns {ReactElement} CopyableCell component
222
- */
223
- const CopyableCell = ({ text, secondary, "data-testid": dataTestId, className, style, }) => {
224
- const [t] = useTranslation();
225
- const copyLabel = t("copyableCell.copy");
226
- const copiedLabel = t("copyableCell.copied");
227
- if (secondary !== undefined) {
228
- return (jsx(MultiRowTableCell, { className: cvaCopyableCell({ className }), "data-testid": dataTestId, main: jsx(CopyableText, { className: "self-start", copiedLabel: copiedLabel, copyLabel: copyLabel, text: text }), secondary: jsx(CopyableText, { className: "self-start", copiedLabel: copiedLabel, copyLabel: copyLabel, size: "xs", text: secondary.text }), style: style }));
229
- }
230
- return (jsx(CopyableText, { className: cvaCopyableCell({ className }), copiedLabel: copiedLabel, copyLabel: copyLabel, "data-testid": dataTestId, style: style, text: text }));
231
- };
232
-
233
152
  const cvaDateTimeCell = cva({ base: [""] });
234
153
  const cvaDateTime$1 = cva({ base: ["slashed-zero", "text-sm", "text-ellipsis"] });
235
154
  const cvaDateTimeSince$1 = cva({ base: ["slashed-zero", "text-neutral-500", "text-xs", "text-ellipsis"] });
@@ -576,6 +495,41 @@ const LinkCell = ({ link, type, className, ref, ...rest }) => {
576
495
  return (jsx(ExternalLink, { className: cvaLinkCell({ className }), href: `${linkPrefix}${link}`, ref: ref, ...rest, color: "neutral", onClick: handleClick, children: link }));
577
496
  };
578
497
 
498
+ const cvaMultiRowTableCellSection = cva({
499
+ base: ["flex", "flex-col", "justify-center", "min-w-0", "max-w-full", "overflow-hidden"],
500
+ });
501
+ const cvaMultiRowTableCell = cva({ base: ["flex", "justify-start", "min-w-0", "max-w-full"] });
502
+ const cvaMainRowText = cva({ base: ["overflow-hidden", "text-ellipsis", "text-sm"] });
503
+ const cvaSecondaryRowText = cva({
504
+ base: ["overflow-hidden", "text-ellipsis", "text-neutral-500", "text-xs"],
505
+ });
506
+
507
+ /**
508
+ * MultiRowTableCell renders two stacked rows inside a single table cell — a primary `main` row
509
+ * and a secondary row below it. String values are automatically styled with appropriate text sizes.
510
+ *
511
+ * ### When to use
512
+ * Use MultiRowTableCell when a table cell needs to display a primary value with secondary metadata below —
513
+ * for example, an asset name with its serial number, or a user name with their email.
514
+ *
515
+ * ### When not to use
516
+ * Do not use MultiRowTableCell for single-line text — use `TextCell`.
517
+ *
518
+ * @example Name with description in a table cell
519
+ * ```tsx
520
+ * import { MultiRowTableCell } from "@trackunit/react-table-base-components";
521
+ *
522
+ * const cell = ({ row }) => (
523
+ * <MultiRowTableCell main={row.original.name} secondary={row.original.serialNumber} />
524
+ * );
525
+ * ```
526
+ * @param {MultiRowTableCellProps} props - The props for the MultiRowTableCell component
527
+ * @returns {ReactElement} MultiRowTableCell component
528
+ */
529
+ const MultiRowTableCell = ({ main, secondary, "data-testid": dataTestId, className, style, }) => {
530
+ return (jsx("div", { className: cvaMultiRowTableCell({ className }), style: style, children: jsxs("section", { className: cvaMultiRowTableCellSection(), "data-testid": dataTestId, children: [typeof main === "string" ? jsx("div", { className: cvaMainRowText(), children: main }) : main, typeof secondary === "string" && secondary !== "" ? (jsx("div", { className: cvaSecondaryRowText(), children: secondary })) : (secondary)] }) }));
531
+ };
532
+
579
533
  /**
580
534
  * Generic list to be used as tooltip content.
581
535
  * The component is data-shape agnostic. It supports primitives, React elements,
@@ -1363,4 +1317,4 @@ const cvaTr = cva({
1363
1317
  */
1364
1318
  setupLibraryTranslations();
1365
1319
 
1366
- export { ButtonCell, CheckboxCell, CopyableCell, DateTimeCell, HighlightCell, IdentityCell, ImageCell, IndicatorCell, LinkCell, ListTooltip, MultiRowTableCell, MultiValueTextCell, NoticeCell, NumberCell, PlainDateCell, ResizeHandle, RowActions, SortIndicator, TableRoot, TagsCell, Tbody, Td, TextCell, Tfoot, Th, Thead, Tr };
1320
+ export { ButtonCell, CheckboxCell, DateTimeCell, HighlightCell, IdentityCell, ImageCell, IndicatorCell, LinkCell, ListTooltip, MultiRowTableCell, MultiValueTextCell, NoticeCell, NumberCell, PlainDateCell, ResizeHandle, RowActions, SortIndicator, TableRoot, TagsCell, Tbody, Td, TextCell, Tfoot, Th, Thead, Tr };
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.insertAttributeIntoOpeningTag = exports.logSummary = exports.parseTsx = exports.hasSpreadAttribute = exports.findJsxAttribute = exports.findJsxElements = exports.getLocalAliasFor = exports.getImportedAliases = exports.visitTsFiles = exports.visitTsxFiles = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const devkit_1 = require("@nx/devkit");
6
+ const ts = tslib_1.__importStar(require("typescript"));
7
+ const TSX_EXTENSIONS = [".tsx", ".jsx"];
8
+ const TS_EXTENSIONS = [".ts", ".tsx", ".jsx"];
9
+ const isTsxFile = (filePath) => TSX_EXTENSIONS.some(ext => filePath.endsWith(ext));
10
+ const isTsFile = (filePath) => TS_EXTENSIONS.some(ext => filePath.endsWith(ext));
11
+ const isUnderNodeModules = (filePath) => filePath.includes("/node_modules/") || filePath.startsWith("node_modules/");
12
+ const isUnderDist = (filePath) => filePath.includes("/dist/") || filePath.startsWith("dist/") || filePath.includes("/.nx/");
13
+ /**
14
+ * Visits every `.tsx`/`.jsx` file under the workspace root and invokes the
15
+ * callback with the file path and its current contents. Files that don't
16
+ * include the marker substring are skipped, which makes large workspaces
17
+ * cheap to scan.
18
+ */
19
+ const visitTsxFiles = (tree, marker, callback) => {
20
+ let touched = 0;
21
+ (0, devkit_1.visitNotIgnoredFiles)(tree, "/", filePath => {
22
+ if (!isTsxFile(filePath))
23
+ return;
24
+ if (isUnderNodeModules(filePath) || isUnderDist(filePath))
25
+ return;
26
+ const content = tree.read(filePath, "utf-8");
27
+ if (content === null)
28
+ return;
29
+ if (!content.includes(marker))
30
+ return;
31
+ const updated = callback(filePath, content);
32
+ if (updated !== null && updated !== undefined && updated !== content) {
33
+ tree.write(filePath, updated);
34
+ touched += 1;
35
+ }
36
+ });
37
+ return touched;
38
+ };
39
+ exports.visitTsxFiles = visitTsxFiles;
40
+ /**
41
+ * Same as {@link visitTsxFiles} but also includes plain `.ts` files. Use this
42
+ * when the codemod also rewrites non-JSX files such as helpers or hooks.
43
+ */
44
+ const visitTsFiles = (tree, marker, callback) => {
45
+ let touched = 0;
46
+ (0, devkit_1.visitNotIgnoredFiles)(tree, "/", filePath => {
47
+ if (!isTsFile(filePath))
48
+ return;
49
+ if (isUnderNodeModules(filePath) || isUnderDist(filePath))
50
+ return;
51
+ const content = tree.read(filePath, "utf-8");
52
+ if (content === null)
53
+ return;
54
+ if (!content.includes(marker))
55
+ return;
56
+ const updated = callback(filePath, content);
57
+ if (updated !== null && updated !== undefined && updated !== content) {
58
+ tree.write(filePath, updated);
59
+ touched += 1;
60
+ }
61
+ });
62
+ return touched;
63
+ };
64
+ exports.visitTsFiles = visitTsFiles;
65
+ /**
66
+ * Mapping of imported component aliases to their original names for a given
67
+ * package. For `import { IconButton as Btn } from "@trackunit/react-components"`
68
+ * this returns `{ Btn: "IconButton" }`.
69
+ *
70
+ * Returns `null` when the file imports nothing from the package, which lets
71
+ * callers short-circuit before parsing JSX.
72
+ */
73
+ const getImportedAliases = (sourceFile, packageName) => {
74
+ const result = {};
75
+ let found = false;
76
+ for (const stmt of sourceFile.statements) {
77
+ if (!ts.isImportDeclaration(stmt))
78
+ continue;
79
+ const moduleSpecifier = stmt.moduleSpecifier;
80
+ if (!ts.isStringLiteral(moduleSpecifier))
81
+ continue;
82
+ if (moduleSpecifier.text !== packageName)
83
+ continue;
84
+ const namedBindings = stmt.importClause?.namedBindings;
85
+ if (namedBindings === undefined || !ts.isNamedImports(namedBindings))
86
+ continue;
87
+ for (const element of namedBindings.elements) {
88
+ const localName = element.name.text;
89
+ const importedName = element.propertyName?.text ?? localName;
90
+ result[localName] = importedName;
91
+ found = true;
92
+ }
93
+ }
94
+ return found ? result : null;
95
+ };
96
+ exports.getImportedAliases = getImportedAliases;
97
+ /**
98
+ * Returns the local alias used in this file for `originalName` when imported
99
+ * from `packageName`, or `null` if the component is not imported.
100
+ */
101
+ const getLocalAliasFor = (sourceFile, packageName, originalName) => {
102
+ const aliases = (0, exports.getImportedAliases)(sourceFile, packageName);
103
+ if (aliases === null)
104
+ return null;
105
+ for (const [local, original] of Object.entries(aliases)) {
106
+ if (original === originalName)
107
+ return local;
108
+ }
109
+ return null;
110
+ };
111
+ exports.getLocalAliasFor = getLocalAliasFor;
112
+ /**
113
+ * Finds every JSX element whose tag name resolves to one of `tagNames`
114
+ * (typically the local aliases returned by {@link getImportedAliases}).
115
+ *
116
+ * The result intentionally includes both opening and full elements so callers
117
+ * can manipulate attributes (via `openingElement`) or wrap/replace the whole
118
+ * element (via `element`).
119
+ */
120
+ const findJsxElements = (sourceFile, tagNames) => {
121
+ const matches = [];
122
+ const tagSet = new Set(tagNames);
123
+ const visit = (node) => {
124
+ if (ts.isJsxSelfClosingElement(node)) {
125
+ if (ts.isIdentifier(node.tagName) && tagSet.has(node.tagName.text)) {
126
+ matches.push({ openingElement: node, element: node });
127
+ }
128
+ }
129
+ else if (ts.isJsxElement(node)) {
130
+ const opening = node.openingElement;
131
+ if (ts.isIdentifier(opening.tagName) && tagSet.has(opening.tagName.text)) {
132
+ matches.push({ openingElement: opening, element: node });
133
+ }
134
+ }
135
+ ts.forEachChild(node, visit);
136
+ };
137
+ visit(sourceFile);
138
+ return matches;
139
+ };
140
+ exports.findJsxElements = findJsxElements;
141
+ /**
142
+ * Looks up a named JSX attribute on the opening element. Returns `null` when
143
+ * the attribute is missing or part of a spread attribute (which we cannot
144
+ * statically inspect).
145
+ */
146
+ const findJsxAttribute = (openingElement, attributeName) => {
147
+ for (const attr of openingElement.attributes.properties) {
148
+ if (!ts.isJsxAttribute(attr))
149
+ continue;
150
+ if (!ts.isIdentifier(attr.name))
151
+ continue;
152
+ if (attr.name.text === attributeName)
153
+ return attr;
154
+ }
155
+ return null;
156
+ };
157
+ exports.findJsxAttribute = findJsxAttribute;
158
+ /**
159
+ * `true` when the element forwards a spread expression such as `{...rest}`,
160
+ * which means we can't be sure which attributes are actually set.
161
+ */
162
+ const hasSpreadAttribute = (openingElement) => {
163
+ return openingElement.attributes.properties.some(prop => ts.isJsxSpreadAttribute(prop));
164
+ };
165
+ exports.hasSpreadAttribute = hasSpreadAttribute;
166
+ /**
167
+ * Parses the file as TSX so JSX is recognised. We do not need a full program
168
+ * here; tsquery's `ast` helper produces a script-kind source file which loses
169
+ * JSX context, so we go through `createSourceFile` directly.
170
+ */
171
+ const parseTsx = (content, fileName = "file.tsx") => ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
172
+ exports.parseTsx = parseTsx;
173
+ /**
174
+ * Helper for codemods to log a one-line summary at the end of a run. Migration
175
+ * runners aggregate logs per migration, so this keeps output focused.
176
+ */
177
+ const logSummary = (migrationName, touched, manualReviewNeeded) => {
178
+ if (touched === 0) {
179
+ devkit_1.logger.info(` ${migrationName}: no files changed`);
180
+ return;
181
+ }
182
+ const reviewSuffix = manualReviewNeeded !== undefined && manualReviewNeeded > 0
183
+ ? ` (${manualReviewNeeded} location(s) need manual review)`
184
+ : "";
185
+ devkit_1.logger.info(` ${migrationName}: updated ${touched} file(s)${reviewSuffix}`);
186
+ };
187
+ exports.logSummary = logSummary;
188
+ /**
189
+ * Inserts an attribute string immediately after the tag name (so the new
190
+ * attribute appears first). Returns the resulting source content.
191
+ *
192
+ * The added attribute is rendered verbatim, so callers are responsible for
193
+ * including the leading space (e.g. ` title="Close"`).
194
+ */
195
+ const insertAttributeIntoOpeningTag = (content, openingElement, attributeText) => {
196
+ const tagName = openingElement.tagName;
197
+ const insertPos = tagName.getEnd();
198
+ return `${content.slice(0, insertPos)} ${attributeText}${content.slice(insertPos)}`;
199
+ };
200
+ exports.insertAttributeIntoOpeningTag = insertAttributeIntoOpeningTag;
201
+ //# sourceMappingURL=jsx-utils.js.map
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.copyableCellToCopyPair = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const ts = tslib_1.__importStar(require("typescript"));
6
+ const jsx_utils_1 = require("../utils/jsx-utils");
7
+ const TABLE_PACKAGE = "@trackunit/react-table-base-components";
8
+ const COMPONENTS_PACKAGE = "@trackunit/react-components";
9
+ const COMPONENT_NAME = "CopyableCell";
10
+ /**
11
+ * The expression a `text`-like attribute resolves to: `text="abc"` yields
12
+ * `"abc"`, `text={value}` yields `value`.
13
+ */
14
+ const textExpression = (attr) => {
15
+ const initializer = attr.initializer;
16
+ if (initializer === undefined)
17
+ return null;
18
+ if (ts.isStringLiteral(initializer))
19
+ return initializer.getText();
20
+ if (ts.isJsxExpression(initializer) && initializer.expression !== undefined) {
21
+ return initializer.expression.getText();
22
+ }
23
+ return null;
24
+ };
25
+ /** For `secondary={{ text: X }}` returns the text of `X`; anything else is null. */
26
+ const secondaryTextExpression = (attr) => {
27
+ const initializer = attr.initializer;
28
+ if (initializer === undefined || !ts.isJsxExpression(initializer) || initializer.expression === undefined) {
29
+ return null;
30
+ }
31
+ const expression = initializer.expression;
32
+ if (!ts.isObjectLiteralExpression(expression))
33
+ return null;
34
+ for (const prop of expression.properties) {
35
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === "text") {
36
+ return prop.initializer.getText();
37
+ }
38
+ }
39
+ return null;
40
+ };
41
+ const renderPair = (textExpr, secondaryRow) => {
42
+ const textProps = [
43
+ `className="min-w-0 truncate${secondaryRow ? " text-neutral-500" : ""}"`,
44
+ secondaryRow ? `size="small"` : null,
45
+ `type="span"`,
46
+ ]
47
+ .filter(Boolean)
48
+ .join(" ");
49
+ return `<div className="flex min-w-0 max-w-full items-center overflow-hidden"><Text ${textProps}>{${textExpr}}</Text><CopyButton className="shrink-0" size="extraSmall" value={${textExpr}} /></div>`;
50
+ };
51
+ const attributeSourceText = (attr) => attr !== null && attr.initializer !== undefined ? attr.initializer.getText() : null;
52
+ const removeNamedImport = (content, packageName, name) => {
53
+ const sourceFile = (0, jsx_utils_1.parseTsx)(content, "source.tsx");
54
+ for (const stmt of sourceFile.statements) {
55
+ if (!ts.isImportDeclaration(stmt))
56
+ continue;
57
+ const moduleSpecifier = stmt.moduleSpecifier;
58
+ if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== packageName)
59
+ continue;
60
+ const namedBindings = stmt.importClause?.namedBindings;
61
+ if (namedBindings === undefined || !ts.isNamedImports(namedBindings))
62
+ continue;
63
+ const remaining = namedBindings.elements.filter(el => (el.propertyName?.text ?? el.name.text) !== name);
64
+ if (remaining.length === namedBindings.elements.length)
65
+ continue;
66
+ if (remaining.length === 0) {
67
+ const start = stmt.getFullStart();
68
+ const end = stmt.getEnd();
69
+ const trailing = content.slice(end).startsWith("\n") ? end + 1 : end;
70
+ return `${content.slice(0, start)}${content.slice(trailing)}`;
71
+ }
72
+ const elementsText = remaining.map(el => el.getText()).join(", ");
73
+ return `${content.slice(0, namedBindings.getStart())}{ ${elementsText} }${content.slice(namedBindings.getEnd())}`;
74
+ }
75
+ return content;
76
+ };
77
+ const ensureNamedImport = (content, packageName, namesToAdd) => {
78
+ const sourceFile = (0, jsx_utils_1.parseTsx)(content, "source.tsx");
79
+ const aliases = (0, jsx_utils_1.getImportedAliases)(sourceFile, packageName) ?? {};
80
+ const missing = namesToAdd.filter(name => !Object.values(aliases).includes(name));
81
+ if (missing.length === 0)
82
+ return content;
83
+ for (const stmt of sourceFile.statements) {
84
+ if (!ts.isImportDeclaration(stmt))
85
+ continue;
86
+ const moduleSpecifier = stmt.moduleSpecifier;
87
+ if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== packageName)
88
+ continue;
89
+ const namedBindings = stmt.importClause?.namedBindings;
90
+ if (namedBindings === undefined || !ts.isNamedImports(namedBindings))
91
+ continue;
92
+ const elementsText = namedBindings.elements.map(el => el.getText()).join(", ");
93
+ const merged = [elementsText, ...missing].join(", ");
94
+ return `${content.slice(0, namedBindings.getStart())}{ ${merged} }${content.slice(namedBindings.getEnd())}`;
95
+ }
96
+ const importLine = `import { ${missing.join(", ")} } from "${packageName}";\n`;
97
+ return `${importLine}${content}`;
98
+ };
99
+ let manualReviewCount = 0;
100
+ const transformCopyableCellUsage = (filePath, content) => {
101
+ const sourceFile = (0, jsx_utils_1.parseTsx)(content, filePath);
102
+ const aliases = (0, jsx_utils_1.getImportedAliases)(sourceFile, TABLE_PACKAGE);
103
+ if (aliases === null)
104
+ return null;
105
+ const cellAlias = Object.entries(aliases).find(([, name]) => name === COMPONENT_NAME)?.[0];
106
+ if (cellAlias === undefined)
107
+ return null;
108
+ if (Object.values(aliases).includes("CopyableCellProps")) {
109
+ manualReviewCount += 1;
110
+ return null;
111
+ }
112
+ const matches = (0, jsx_utils_1.findJsxElements)(sourceFile, [cellAlias]);
113
+ if (matches.length === 0)
114
+ return null;
115
+ const edits = [];
116
+ let needsMultiRow = false;
117
+ for (const { openingElement, element } of matches) {
118
+ if ((0, jsx_utils_1.hasSpreadAttribute)(openingElement)) {
119
+ manualReviewCount += 1;
120
+ continue;
121
+ }
122
+ const textAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "text");
123
+ const textExpr = textAttr !== null ? textExpression(textAttr) : null;
124
+ if (textExpr === null) {
125
+ manualReviewCount += 1;
126
+ continue;
127
+ }
128
+ const classNameAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "className");
129
+ if (classNameAttr !== null) {
130
+ // The old cell forwarded className to its own cva root; there is no
131
+ // 1:1 slot for it on the pair, so leave these for a human.
132
+ manualReviewCount += 1;
133
+ continue;
134
+ }
135
+ const secondaryAttr = (0, jsx_utils_1.findJsxAttribute)(openingElement, "secondary");
136
+ const testIdText = attributeSourceText((0, jsx_utils_1.findJsxAttribute)(openingElement, "data-testid"));
137
+ let replacement;
138
+ if (secondaryAttr === null) {
139
+ replacement = renderPair(textExpr, false);
140
+ if (testIdText !== null) {
141
+ replacement = replacement.replace(`<div className="flex min-w-0 max-w-full items-center overflow-hidden">`, `<div className="flex min-w-0 max-w-full items-center overflow-hidden" data-testid=${testIdText}>`);
142
+ }
143
+ }
144
+ else {
145
+ const secondaryExpr = secondaryTextExpression(secondaryAttr);
146
+ if (secondaryExpr === null) {
147
+ // `secondary` was something other than an inline `{{ text: … }}`.
148
+ manualReviewCount += 1;
149
+ continue;
150
+ }
151
+ needsMultiRow = true;
152
+ const testIdProp = testIdText !== null ? ` data-testid=${testIdText}` : "";
153
+ replacement = `<MultiRowTableCell className="w-fit overflow-hidden"${testIdProp} main={${renderPair(textExpr, false)}} secondary={${renderPair(secondaryExpr, true)}} />`;
154
+ }
155
+ edits.push({ start: element.getStart(), end: element.getEnd(), replacement });
156
+ }
157
+ if (edits.length === 0)
158
+ return null;
159
+ edits.sort((a, b) => b.start - a.start);
160
+ let updated = content;
161
+ for (const { start, end, replacement } of edits) {
162
+ updated = `${updated.slice(0, start)}${replacement}${updated.slice(end)}`;
163
+ }
164
+ const remaining = (0, jsx_utils_1.findJsxElements)((0, jsx_utils_1.parseTsx)(updated, filePath), [cellAlias]);
165
+ if (remaining.length === 0) {
166
+ updated = removeNamedImport(updated, TABLE_PACKAGE, COMPONENT_NAME);
167
+ }
168
+ else {
169
+ manualReviewCount += 1;
170
+ }
171
+ if (needsMultiRow) {
172
+ updated = ensureNamedImport(updated, TABLE_PACKAGE, ["MultiRowTableCell"]);
173
+ }
174
+ updated = ensureNamedImport(updated, COMPONENTS_PACKAGE, ["Text", "CopyButton"]);
175
+ return updated;
176
+ };
177
+ /**
178
+ * Rewrites `<CopyableCell text={x} secondary={{ text: y }} …/>` (removed in
179
+ * v3) into the value/copy pair used by table cells: a truncating `Text` next
180
+ * to an `extraSmall` `CopyButton`, wrapped for two-row cells in
181
+ * `MultiRowTableCell` with the `w-fit overflow-hidden` guard that keeps the
182
+ * pair inside the cell's grid track. Usages with spreads, dynamic
183
+ * `secondary` values, forwarded classNames, or a referenced
184
+ * `CopyableCellProps` type are left for manual review.
185
+ */
186
+ const copyableCellToCopyPair = (tree) => {
187
+ manualReviewCount = 0;
188
+ const touched = (0, jsx_utils_1.visitTsxFiles)(tree, COMPONENT_NAME, transformCopyableCellUsage);
189
+ (0, jsx_utils_1.logSummary)("copyablecell-to-copy-pair", touched, manualReviewCount);
190
+ };
191
+ exports.copyableCellToCopyPair = copyableCellToCopyPair;
192
+ exports.default = exports.copyableCellToCopyPair;
193
+ //# sourceMappingURL=copyablecell-to-copy-pair.js.map
package/migrations.json CHANGED
@@ -1,3 +1,9 @@
1
1
  {
2
- "generators": {}
2
+ "generators": {
3
+ "v3-0-0-copyablecell-to-copy-pair": {
4
+ "version": "3.0.0",
5
+ "description": "Replace the removed CopyableCell with the Text + CopyButton pair, wrapped in MultiRowTableCell for two-row cells.",
6
+ "implementation": "./migrations/v3-0-0/copyablecell-to-copy-pair"
7
+ }
8
+ }
3
9
  }
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@trackunit/react-table-base-components",
3
- "version": "2.7.22-alpha-15da733192f.0",
3
+ "version": "2.8.1",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
7
7
  "node": ">=24.x"
8
8
  },
9
9
  "dependencies": {
10
- "@trackunit/react-components": "2.13.28-alpha-15da733192f.0",
11
- "@trackunit/ui-icons": "1.14.45-alpha-15da733192f.0",
12
- "@trackunit/react-form-components": "2.6.76-alpha-15da733192f.0",
13
- "@trackunit/css-class-variance-utilities": "2.0.16-alpha-15da733192f.0",
14
- "@trackunit/date-and-time-utils": "1.14.47-alpha-15da733192f.0",
15
- "@trackunit/react-date-and-time-hooks": "2.6.74-alpha-15da733192f.0",
16
- "@trackunit/shared-utils": "1.16.51-alpha-15da733192f.0",
17
- "@trackunit/i18n-library-translation": "2.4.66-alpha-15da733192f.0",
10
+ "@trackunit/react-components": "3.0.1",
11
+ "@trackunit/ui-icons": "1.14.44",
12
+ "@trackunit/react-form-components": "2.6.77",
13
+ "@trackunit/css-class-variance-utilities": "2.0.15",
14
+ "@trackunit/date-and-time-utils": "1.14.46",
15
+ "@trackunit/react-date-and-time-hooks": "2.6.75",
16
+ "@trackunit/shared-utils": "1.16.50",
17
+ "@trackunit/i18n-library-translation": "2.4.65",
18
18
  "tailwind-merge": "^2.0.0"
19
19
  },
20
20
  "peerDependencies": {
package/src/index.d.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  export * from "./components/ButtonCell/ButtonCell";
2
2
  export * from "./components/CheckboxCell/CheckboxCell";
3
- export { CopyableCell } from "./components/CopyableCell/CopyableCell";
4
- export type { CopyableCellProps } from "./components/CopyableCell/CopyableCell";
5
3
  export * from "./components/DateTimeCell/DateTimeCell";
6
4
  export { HighlightCell } from "./components/HighlightCell/HighlightCell";
7
5
  export type { HighlightCellProps } from "./components/HighlightCell/HighlightCell";
@@ -6,8 +6,8 @@ export declare const translations: TranslationResource<TranslationKeys>;
6
6
  /**
7
7
  * Local useTranslation for this specific library
8
8
  */
9
- export declare const useTranslation: () => [TransForLibs<"copyableCell.copied" | "copyableCell.copy" | "rowActions.moreActions">, import("i18next").i18n, boolean] & {
10
- t: TransForLibs<"copyableCell.copied" | "copyableCell.copy" | "rowActions.moreActions">;
9
+ export declare const useTranslation: () => [TransForLibs<"rowActions.moreActions">, import("i18next").i18n, boolean] & {
10
+ t: TransForLibs<"rowActions.moreActions">;
11
11
  i18n: import("i18next").i18n;
12
12
  ready: boolean;
13
13
  };
@@ -1 +0,0 @@
1
- {"version":3,"file":"entry.js","sourceRoot":"","sources":["../../../../../libs/react/table-base-components/migrations/entry.ts"],"names":[],"mappings":"","sourcesContent":["export {};\n"]}
@@ -1,44 +0,0 @@
1
- import { CommonProps, type CopyableTextProps, type Styleable } from "@trackunit/react-components";
2
- import type { ReactElement } from "react";
3
- type CopyableCellSecondary = Pick<CopyableTextProps, "text">;
4
- export interface CopyableCellProps extends CommonProps, Pick<CopyableTextProps, "text">, Styleable {
5
- /**
6
- * When provided, renders a multi-line cell with a secondary row below the primary.
7
- * The secondary row is independently copyable.
8
- */
9
- readonly secondary?: CopyableCellSecondary;
10
- }
11
- /**
12
- * CopyableCell renders a table cell with click-to-copy functionality.
13
- * Supports single-line and multi-line layouts.
14
- *
15
- * ### When to use
16
- * Use CopyableCell inside table column definitions when a cell value (ID, name, reference) should be copyable.
17
- *
18
- * ### When not to use
19
- * For standalone copyable text outside of tables, use `CopyableText` from `@trackunit/react-components`.
20
- *
21
- * @example Single-line copyable cell
22
- * ```tsx
23
- * import { CopyableCell } from "@trackunit/react-table-base-components";
24
- *
25
- * const cell = ({ getValue }) => (
26
- * <CopyableCell text={getValue()} />
27
- * );
28
- * ```
29
- * @example Multi-line copyable cell
30
- * ```tsx
31
- * import { CopyableCell } from "@trackunit/react-table-base-components";
32
- *
33
- * const cell = ({ row }) => (
34
- * <CopyableCell
35
- * text={String(row.original.customerId)}
36
- * secondary={{ text: row.original.name }}
37
- * />
38
- * );
39
- * ```
40
- * @param {CopyableCellProps} props - The props for the CopyableCell component
41
- * @returns {ReactElement} CopyableCell component
42
- */
43
- export declare const CopyableCell: ({ text, secondary, "data-testid": dataTestId, className, style, }: CopyableCellProps) => ReactElement;
44
- export {};
@@ -1,6 +0,0 @@
1
- export declare const cvaCopyableCell: import("cva").CVAComponent<Omit<{
2
- base: string[];
3
- }, "defaultVariants"> & {
4
- variants: unknown;
5
- defaultVariants: Omit<{}, never>;
6
- }, unknown>;