@carbon/upgrade 11.41.0 → 11.42.0

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.
@@ -0,0 +1,442 @@
1
+ /**
2
+ * Copyright IBM Corp. 2024, 2025
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * Transforms Tearsheet to preview__Tearsheet with composable API:
8
+ *
9
+ * Before:
10
+ * <Tearsheet
11
+ * title="Title"
12
+ * label="Label"
13
+ * actions={[...]}
14
+ * influencer={<Component />}
15
+ * >
16
+ * {children}
17
+ * </Tearsheet>
18
+ *
19
+ * After:
20
+ * <Tearsheet>
21
+ * <Tearsheet.Header>
22
+ * <Tearsheet.HeaderContent title="Title" label="Label" />
23
+ * </Tearsheet.Header>
24
+ * <Tearsheet.Influencer><Component /></Tearsheet.Influencer>
25
+ * <Tearsheet.Body>
26
+ * <Tearsheet.MainContent>{children}</Tearsheet.MainContent>
27
+ * </Tearsheet.Body>
28
+ * <Tearsheet.Footer actions={[...]} />
29
+ * </Tearsheet>
30
+ */
31
+
32
+ 'use strict';
33
+
34
+ // Props that stay on the root Tearsheet component
35
+ const ROOT_PROPS = [
36
+ 'open',
37
+ 'onClose',
38
+ 'variant',
39
+ 'decorator',
40
+ 'influencerWidth',
41
+ 'summaryContentWidth',
42
+ 'verticalGap',
43
+ 'preventCloseOnClickOutside',
44
+ 'launcherButtonRef',
45
+ 'selectorPrimaryFocus',
46
+ 'keepMounted',
47
+ 'disablePortal',
48
+ 'className',
49
+ 'portalTarget',
50
+ 'height',
51
+ 'hasCloseIcon',
52
+ ];
53
+
54
+ // Props that move to Tearsheet.HeaderContent
55
+ const HEADER_CONTENT_PROPS = ['title', 'label', 'description', 'headerActions'];
56
+
57
+ // Props that move to Tearsheet.Header
58
+ const HEADER_PROPS = [
59
+ 'hideCloseButton',
60
+ 'disableHeaderCollapse',
61
+ 'closeIconDescription',
62
+ ];
63
+
64
+ // Props that move to Tearsheet.Footer
65
+ const FOOTER_PROPS = ['actions'];
66
+
67
+ /**
68
+ * Helper to create JSX member expression (e.g., Tearsheet.Header)
69
+ */
70
+ function createMemberExpression(j, localName, property) {
71
+ return j.jsxMemberExpression(
72
+ j.jsxIdentifier(localName),
73
+ j.jsxIdentifier(property)
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Helper to extract JSX value from attribute
79
+ */
80
+ function extractJSXValue(attributeValue) {
81
+ if (!attributeValue) return null;
82
+
83
+ if (attributeValue.type === 'JSXExpressionContainer') {
84
+ return attributeValue.expression;
85
+ }
86
+
87
+ return attributeValue;
88
+ }
89
+
90
+ /**
91
+ * Create Tearsheet.Header with HeaderContent
92
+ */
93
+ function createHeader(j, localName, headerProps, headerContentProps) {
94
+ const hasHeaderContentProps = headerContentProps.length > 0;
95
+ const hasHeaderProps = headerProps.length > 0;
96
+
97
+ if (!hasHeaderContentProps && !hasHeaderProps) {
98
+ return null;
99
+ }
100
+
101
+ // Create HeaderContent
102
+ const headerContent = j.jsxElement(
103
+ j.jsxOpeningElement(
104
+ createMemberExpression(j, localName, 'HeaderContent'),
105
+ headerContentProps,
106
+ headerContentProps.length === 0
107
+ ),
108
+ headerContentProps.length === 0
109
+ ? null
110
+ : j.jsxClosingElement(
111
+ createMemberExpression(j, localName, 'HeaderContent')
112
+ ),
113
+ []
114
+ );
115
+
116
+ // Create Header wrapping HeaderContent
117
+ const header = j.jsxElement(
118
+ j.jsxOpeningElement(
119
+ createMemberExpression(j, localName, 'Header'),
120
+ headerProps
121
+ ),
122
+ j.jsxClosingElement(createMemberExpression(j, localName, 'Header')),
123
+ [j.jsxText('\n '), headerContent, j.jsxText('\n ')]
124
+ );
125
+
126
+ return header;
127
+ }
128
+
129
+ /**
130
+ * Create Tearsheet.Influencer
131
+ */
132
+ function createInfluencer(j, localName, influencerValue) {
133
+ if (!influencerValue) return null;
134
+
135
+ const content = extractJSXValue(influencerValue);
136
+ if (!content) return null;
137
+
138
+ return j.jsxElement(
139
+ j.jsxOpeningElement(createMemberExpression(j, localName, 'Influencer'), []),
140
+ j.jsxClosingElement(createMemberExpression(j, localName, 'Influencer')),
141
+ [j.jsxText('\n '), content, j.jsxText('\n ')]
142
+ );
143
+ }
144
+
145
+ /**
146
+ * Create Tearsheet.NavigationBar
147
+ */
148
+ function createNavigationBar(j, localName, navigationValue) {
149
+ if (!navigationValue) return null;
150
+
151
+ const content = extractJSXValue(navigationValue);
152
+ if (!content) return null;
153
+
154
+ return j.jsxElement(
155
+ j.jsxOpeningElement(
156
+ createMemberExpression(j, localName, 'NavigationBar'),
157
+ []
158
+ ),
159
+ j.jsxClosingElement(createMemberExpression(j, localName, 'NavigationBar')),
160
+ [j.jsxText('\n '), content, j.jsxText('\n ')]
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Create Tearsheet.Body with MainContent
166
+ */
167
+ function createBody(j, localName, originalChildren) {
168
+ // Create MainContent with original children
169
+ const mainContent = j.jsxElement(
170
+ j.jsxOpeningElement(
171
+ createMemberExpression(j, localName, 'MainContent'),
172
+ []
173
+ ),
174
+ j.jsxClosingElement(createMemberExpression(j, localName, 'MainContent')),
175
+ originalChildren.length > 0
176
+ ? [j.jsxText('\n '), ...originalChildren, j.jsxText('\n ')]
177
+ : []
178
+ );
179
+
180
+ // Create Body wrapping MainContent
181
+ const body = j.jsxElement(
182
+ j.jsxOpeningElement(createMemberExpression(j, localName, 'Body'), []),
183
+ j.jsxClosingElement(createMemberExpression(j, localName, 'Body')),
184
+ [j.jsxText('\n '), mainContent, j.jsxText('\n ')]
185
+ );
186
+
187
+ return body;
188
+ }
189
+
190
+ /**
191
+ * Create Tearsheet.Footer
192
+ */
193
+ function createFooter(j, localName, footerProps) {
194
+ if (footerProps.length === 0) return null;
195
+
196
+ return j.jsxElement(
197
+ j.jsxOpeningElement(
198
+ createMemberExpression(j, localName, 'Footer'),
199
+ footerProps,
200
+ true // self-closing
201
+ )
202
+ );
203
+ }
204
+
205
+ /**
206
+ * Transform Tearsheet JSX element to composable structure
207
+ */
208
+ function transformTearsheetElement(j, path, localName) {
209
+ const element = path.node;
210
+ const openingElement = element.openingElement;
211
+ const attributes = openingElement.attributes || [];
212
+ const originalChildren = element.children || [];
213
+
214
+ // Categorize props
215
+ const rootProps = [];
216
+ const headerProps = [];
217
+ const headerContentProps = [];
218
+ const footerProps = [];
219
+ let influencerValue = null;
220
+ let navigationValue = null;
221
+
222
+ attributes.forEach((attr) => {
223
+ if (attr.type !== 'JSXAttribute') {
224
+ // Handle spread attributes
225
+ rootProps.push(attr);
226
+ return;
227
+ }
228
+
229
+ const propName = attr.name.name;
230
+
231
+ // Handle deprecated props
232
+ if (propName === 'slug') {
233
+ // Rename slug to decorator
234
+ attr.name.name = 'decorator';
235
+ rootProps.push(attr);
236
+ return;
237
+ }
238
+
239
+ if (propName === 'influencerPosition') {
240
+ // Skip - no longer supported
241
+ return;
242
+ }
243
+
244
+ // Categorize props
245
+ if (ROOT_PROPS.includes(propName)) {
246
+ rootProps.push(attr);
247
+ } else if (HEADER_PROPS.includes(propName)) {
248
+ headerProps.push(attr);
249
+ } else if (HEADER_CONTENT_PROPS.includes(propName)) {
250
+ headerContentProps.push(attr);
251
+ } else if (propName === 'influencer') {
252
+ influencerValue = attr.value;
253
+ } else if (propName === 'navigation') {
254
+ navigationValue = attr.value;
255
+ } else if (FOOTER_PROPS.includes(propName)) {
256
+ footerProps.push(attr);
257
+ } else {
258
+ // Unknown props stay on root
259
+ rootProps.push(attr);
260
+ }
261
+ });
262
+
263
+ // Build new composable structure
264
+ const newChildren = [];
265
+
266
+ // Add Header (if needed)
267
+ const header = createHeader(j, localName, headerProps, headerContentProps);
268
+ if (header) {
269
+ newChildren.push(j.jsxText('\n '), header);
270
+ }
271
+
272
+ // Add Influencer (if needed)
273
+ const influencer = createInfluencer(j, localName, influencerValue);
274
+ if (influencer) {
275
+ newChildren.push(j.jsxText('\n '), influencer);
276
+ }
277
+
278
+ // Add NavigationBar (if needed)
279
+ const navigationBar = createNavigationBar(j, localName, navigationValue);
280
+ if (navigationBar) {
281
+ newChildren.push(j.jsxText('\n '), navigationBar);
282
+ }
283
+
284
+ // Add Body (always)
285
+ const body = createBody(j, localName, originalChildren);
286
+ newChildren.push(j.jsxText('\n '), body);
287
+
288
+ // Add Footer (if needed)
289
+ const footer = createFooter(j, localName, footerProps);
290
+ if (footer) {
291
+ newChildren.push(j.jsxText('\n '), footer);
292
+ }
293
+
294
+ // Add final newline
295
+ if (newChildren.length > 0) {
296
+ newChildren.push(j.jsxText('\n'));
297
+ }
298
+
299
+ // Create new Tearsheet element with composable structure
300
+ const newElement = j.jsxElement(
301
+ j.jsxOpeningElement(j.jsxIdentifier(localName), rootProps),
302
+ j.jsxClosingElement(j.jsxIdentifier(localName)),
303
+ newChildren
304
+ );
305
+
306
+ return newElement;
307
+ }
308
+
309
+ function transform(fileInfo, api) {
310
+ const j = api.jscodeshift;
311
+ const root = j(fileInfo.source);
312
+
313
+ let jsxTransformed = false;
314
+
315
+ // Track local names for Tearsheet imports and their original imported names
316
+ const tearsheetLocalNames = new Map(); // localName -> importedName
317
+
318
+ // First pass: identify Tearsheet imports and track local names
319
+ root
320
+ .find(j.ImportDeclaration, {
321
+ source: {
322
+ value: '@carbon/ibm-products',
323
+ },
324
+ })
325
+ .forEach((path) => {
326
+ path.node.specifiers.forEach((specifier) => {
327
+ if (specifier.type === 'ImportSpecifier') {
328
+ const importedName = specifier.imported.name;
329
+ const localName = specifier.local
330
+ ? specifier.local.name
331
+ : importedName;
332
+
333
+ // Track Tearsheet imports
334
+ if (
335
+ importedName === 'Tearsheet' ||
336
+ importedName === 'preview__Tearsheet'
337
+ ) {
338
+ tearsheetLocalNames.set(localName, importedName);
339
+ }
340
+ }
341
+ });
342
+ });
343
+
344
+ // Second pass: Transform Tearsheet JSX elements
345
+ root.find(j.JSXElement).forEach((path) => {
346
+ const openingElement = path.node.openingElement;
347
+ if (openingElement.name.type === 'JSXIdentifier') {
348
+ const elementName = openingElement.name.name;
349
+
350
+ // Check if this element uses a Tearsheet local name
351
+ if (tearsheetLocalNames.has(elementName)) {
352
+ // Check if already using composable API (idempotency check)
353
+ const hasComposableChildren = path.node.children?.some((child) => {
354
+ if (child.type === 'JSXElement') {
355
+ const childName = child.openingElement.name;
356
+ if (childName.type === 'JSXMemberExpression') {
357
+ const objectName = childName.object.name;
358
+ const propertyName = childName.property.name;
359
+ // Check if child is Tearsheet.Header, Tearsheet.Body, etc.
360
+ return (
361
+ objectName === elementName &&
362
+ [
363
+ 'Header',
364
+ 'Body',
365
+ 'Footer',
366
+ 'Influencer',
367
+ 'NavigationBar',
368
+ ].includes(propertyName)
369
+ );
370
+ }
371
+ }
372
+ return false;
373
+ });
374
+
375
+ // Only transform if not already using composable API
376
+ if (!hasComposableChildren) {
377
+ const newElement = transformTearsheetElement(j, path, elementName);
378
+ j(path).replaceWith(newElement);
379
+ jsxTransformed = true;
380
+ }
381
+ }
382
+ }
383
+ });
384
+
385
+ // Third pass: Only transform imports if JSX was transformed
386
+ if (jsxTransformed) {
387
+ root
388
+ .find(j.ImportDeclaration, {
389
+ source: {
390
+ value: '@carbon/ibm-products',
391
+ },
392
+ })
393
+ .forEach((path) => {
394
+ const seen = new Set();
395
+ const newSpecifiers = [];
396
+
397
+ path.node.specifiers.forEach((specifier) => {
398
+ if (specifier.type === 'ImportSpecifier') {
399
+ const importedName = specifier.imported.name;
400
+ const localName = specifier.local
401
+ ? specifier.local.name
402
+ : importedName;
403
+
404
+ // Check if this is Tearsheet
405
+ if (
406
+ importedName === 'Tearsheet' ||
407
+ importedName === 'preview__Tearsheet'
408
+ ) {
409
+ // Transform to preview__Tearsheet as localName
410
+ const newImported = j.identifier('preview__Tearsheet');
411
+ const newLocal = j.identifier(localName);
412
+ const newSpecifier = j.importSpecifier(newImported, newLocal);
413
+
414
+ if (!seen.has(localName)) {
415
+ newSpecifiers.push(newSpecifier);
416
+ seen.add(localName);
417
+ }
418
+ } else {
419
+ // Keep other imports as-is
420
+ if (!seen.has(localName)) {
421
+ newSpecifiers.push(specifier);
422
+ seen.add(localName);
423
+ }
424
+ }
425
+ } else {
426
+ // Keep default imports and namespace imports
427
+ newSpecifiers.push(specifier);
428
+ }
429
+ });
430
+
431
+ path.node.specifiers = newSpecifiers;
432
+ });
433
+
434
+ return root.toSource({ quote: 'single', trailingComma: true });
435
+ }
436
+
437
+ // Return original source if no transformations were made
438
+ return fileInfo.source;
439
+ }
440
+
441
+ module.exports = transform;
442
+ module.exports.parser = 'tsx';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright IBM Corp. 2025, 2025
2
+ * Copyright IBM Corp. 2025, 2026
3
3
  *
4
4
  * This source code is licensed under the Apache-2.0 license found in the
5
5
  * LICENSE file in the root directory of this source tree.