@carbon/upgrade 11.40.0 → 11.41.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,391 @@
1
+ /**
2
+ * Copyright IBM Corp. 2026
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
+
8
+ /**
9
+ * Rewrites stable PageHeader to composable PageHeader
10
+ *
11
+ * Transforms:
12
+ *
13
+ * <PageHeader
14
+ * title="Page title"
15
+ * subtitle="Optional subtitle"
16
+ * breadcrumbs={[...]}
17
+ * pageActions={[...]}
18
+ * actionBarItems={[...]}
19
+ * navigation={<Tabs>...</Tabs>}
20
+ * tags={[...]}
21
+ * >
22
+ * <p>Content</p>
23
+ * </PageHeader>
24
+ *
25
+ * Into:
26
+ *
27
+ * <PageHeader>
28
+ * <PageHeader.BreadcrumbBar breadcrumbs={[...]} />
29
+ * <PageHeader.Content title="Page title" pageActions={[...]} contextualActions={[...]}>
30
+ * <PageHeader.ContentText subtitle="Optional subtitle" />
31
+ * <p>Content</p>
32
+ * </PageHeader.Content>
33
+ * <PageHeader.TabBar>{<Tabs>...</Tabs>}</PageHeader.TabBar>
34
+ * <PageHeader.TagOverflow tags={[...]} />
35
+ * </PageHeader>
36
+ */
37
+
38
+ 'use strict';
39
+
40
+ const transform = (fileInfo, api) => {
41
+ const j = api.jscodeshift;
42
+ const root = j(fileInfo.source);
43
+ let dirtyFlag = false;
44
+
45
+ // Find the local name used for PageHeader import (could be aliased)
46
+ let pageHeaderLocalName = null;
47
+ root
48
+ .find(j.ImportDeclaration)
49
+ .filter((path) => path.node.source.value === '@carbon/ibm-products')
50
+ .forEach((path) => {
51
+ path.node.specifiers.forEach((specifier) => {
52
+ if (
53
+ specifier.type === 'ImportSpecifier' &&
54
+ specifier.imported &&
55
+ specifier.imported.name === 'PageHeader'
56
+ ) {
57
+ pageHeaderLocalName = specifier.local
58
+ ? specifier.local.name
59
+ : 'PageHeader';
60
+ }
61
+ });
62
+ });
63
+
64
+ // If PageHeader is not imported, nothing to transform
65
+ if (!pageHeaderLocalName) {
66
+ return root.toSource();
67
+ }
68
+
69
+ // Helper to create JSX member expression (e.g., PageHeader.Content)
70
+ const createMemberExpression = (object, property) => {
71
+ return j.jsxMemberExpression(
72
+ j.jsxIdentifier(object),
73
+ j.jsxIdentifier(property)
74
+ );
75
+ };
76
+
77
+ // Helper to create JSX element with member expression
78
+ const createMemberJSXElement = (object, property, attributes, children) => {
79
+ const memberExpression = createMemberExpression(object, property);
80
+ const openingElement = j.jsxOpeningElement(
81
+ memberExpression,
82
+ attributes,
83
+ children.length === 0
84
+ );
85
+ return children.length === 0
86
+ ? j.jsxElement(openingElement, null, [])
87
+ : j.jsxElement(
88
+ openingElement,
89
+ j.jsxClosingElement(memberExpression),
90
+ children
91
+ );
92
+ };
93
+
94
+ // Transform PageHeader components (using the local name which could be an alias)
95
+ root
96
+ .find(j.JSXElement, {
97
+ openingElement: { name: { name: pageHeaderLocalName } },
98
+ })
99
+ .forEach((path) => {
100
+ const attributes = path.node.openingElement.attributes;
101
+ const originalChildren = path.node.children || [];
102
+
103
+ // Props to extract
104
+ let titleProp = null;
105
+ let subtitleProp = null;
106
+ let breadcrumbsProp = null;
107
+ let breadcrumbOverflowAriaLabelProp = null;
108
+ let pageActionsProp = null;
109
+ let actionBarItemsProp = null;
110
+ let navigationProp = null;
111
+ let tagsProp = null;
112
+ let showAllTagsLabelProp = null;
113
+ let allTagsModalTitleProp = null;
114
+ let allTagsModalSearchLabelProp = null;
115
+ let allTagsModalSearchPlaceholderTextProp = null;
116
+
117
+ // Props to keep on root PageHeader
118
+ const rootAttributes = [];
119
+
120
+ // Deprecated props to ignore
121
+ const deprecatedProps = [
122
+ 'collapseHeader',
123
+ 'collapseHeaderIconDescription',
124
+ 'expandHeaderIconDescription',
125
+ 'hasCollapseHeaderToggle',
126
+ 'enableBreadcrumbScroll',
127
+ 'withoutBackground',
128
+ ];
129
+
130
+ // Process attributes
131
+ attributes.forEach((attr) => {
132
+ if (attr.type !== 'JSXAttribute') {
133
+ rootAttributes.push(attr);
134
+ return;
135
+ }
136
+
137
+ const attrName = attr.name.name;
138
+
139
+ // Extract props for transformation
140
+ if (attrName === 'title') {
141
+ titleProp = attr;
142
+ } else if (attrName === 'subtitle') {
143
+ subtitleProp = attr;
144
+ } else if (attrName === 'breadcrumbs') {
145
+ breadcrumbsProp = attr;
146
+ } else if (attrName === 'breadcrumbOverflowAriaLabel') {
147
+ breadcrumbOverflowAriaLabelProp = attr;
148
+ } else if (attrName === 'pageActions') {
149
+ pageActionsProp = attr;
150
+ } else if (attrName === 'actionBarItems') {
151
+ actionBarItemsProp = attr;
152
+ } else if (attrName === 'navigation') {
153
+ navigationProp = attr;
154
+ } else if (attrName === 'tags') {
155
+ tagsProp = attr;
156
+ } else if (attrName === 'showAllTagsLabel') {
157
+ showAllTagsLabelProp = attr;
158
+ } else if (attrName === 'allTagsModalTitle') {
159
+ allTagsModalTitleProp = attr;
160
+ } else if (attrName === 'allTagsModalSearchLabel') {
161
+ allTagsModalSearchLabelProp = attr;
162
+ } else if (attrName === 'allTagsModalSearchPlaceholderText') {
163
+ allTagsModalSearchPlaceholderTextProp = attr;
164
+ } else if (deprecatedProps.includes(attrName)) {
165
+ // Ignore deprecated props
166
+ } else {
167
+ // Keep other props on root
168
+ rootAttributes.push(attr);
169
+ }
170
+ });
171
+
172
+ // Only transform if we have any props that need migration
173
+ const needsTransformation =
174
+ titleProp ||
175
+ subtitleProp ||
176
+ breadcrumbsProp ||
177
+ pageActionsProp ||
178
+ actionBarItemsProp ||
179
+ navigationProp ||
180
+ tagsProp;
181
+
182
+ if (!needsTransformation) {
183
+ return; // Skip this PageHeader, no transformation needed
184
+ }
185
+
186
+ dirtyFlag = true;
187
+
188
+ const newChildren = [];
189
+
190
+ // 1. Add BreadcrumbBar if breadcrumbs exist
191
+ if (breadcrumbsProp) {
192
+ const breadcrumbBarAttrs = [breadcrumbsProp];
193
+ if (breadcrumbOverflowAriaLabelProp) {
194
+ breadcrumbBarAttrs.push(breadcrumbOverflowAriaLabelProp);
195
+ }
196
+ newChildren.push(
197
+ createMemberJSXElement(
198
+ pageHeaderLocalName,
199
+ 'BreadcrumbBar',
200
+ breadcrumbBarAttrs,
201
+ []
202
+ )
203
+ );
204
+ }
205
+
206
+ // 2. Add Content if title exists
207
+ if (titleProp) {
208
+ const contentAttrs = [titleProp];
209
+
210
+ // Handle title as object with icon
211
+ if (
212
+ titleProp.value &&
213
+ titleProp.value.type === 'JSXExpressionContainer'
214
+ ) {
215
+ const titleExpr = titleProp.value.expression;
216
+ if (titleExpr.type === 'ObjectExpression') {
217
+ // Extract text and icon from title object
218
+ const textProp = titleExpr.properties.find(
219
+ (p) => p.key && p.key.name === 'text'
220
+ );
221
+ const iconProp = titleExpr.properties.find(
222
+ (p) => p.key && p.key.name === 'icon'
223
+ );
224
+
225
+ if (textProp) {
226
+ // Replace title with just the text value
227
+ contentAttrs[0] = j.jsxAttribute(
228
+ j.jsxIdentifier('title'),
229
+ j.jsxExpressionContainer(textProp.value)
230
+ );
231
+ }
232
+
233
+ if (iconProp) {
234
+ // Add renderIcon prop
235
+ contentAttrs.push(
236
+ j.jsxAttribute(
237
+ j.jsxIdentifier('renderIcon'),
238
+ j.jsxExpressionContainer(iconProp.value)
239
+ )
240
+ );
241
+ }
242
+ }
243
+ }
244
+
245
+ // Add pageActions if exists
246
+ if (pageActionsProp) {
247
+ contentAttrs.push(pageActionsProp);
248
+ }
249
+
250
+ // Add contextualActions (from actionBarItems) if exists
251
+ if (actionBarItemsProp) {
252
+ const contextualActionsAttr = j.jsxAttribute(
253
+ j.jsxIdentifier('contextualActions'),
254
+ actionBarItemsProp.value
255
+ );
256
+ contentAttrs.push(contextualActionsAttr);
257
+ }
258
+
259
+ const contentChildren = [];
260
+
261
+ // Add ContentText if subtitle exists
262
+ if (subtitleProp) {
263
+ contentChildren.push(
264
+ createMemberJSXElement(
265
+ pageHeaderLocalName,
266
+ 'ContentText',
267
+ [subtitleProp],
268
+ []
269
+ )
270
+ );
271
+ }
272
+
273
+ // Add original children to Content
274
+ contentChildren.push(...originalChildren);
275
+
276
+ newChildren.push(
277
+ createMemberJSXElement(
278
+ pageHeaderLocalName,
279
+ 'Content',
280
+ contentAttrs,
281
+ contentChildren
282
+ )
283
+ );
284
+ }
285
+
286
+ // 3. Add TabBar if navigation exists
287
+ if (navigationProp) {
288
+ const tabBarChildren = [];
289
+ if (navigationProp.value.type === 'JSXExpressionContainer') {
290
+ tabBarChildren.push(navigationProp.value.expression);
291
+ }
292
+ newChildren.push(
293
+ createMemberJSXElement(
294
+ pageHeaderLocalName,
295
+ 'TabBar',
296
+ [],
297
+ tabBarChildren
298
+ )
299
+ );
300
+ }
301
+
302
+ // 4. Add TagOverflow if tags exist
303
+ if (tagsProp) {
304
+ const tagOverflowAttrs = [tagsProp];
305
+ if (showAllTagsLabelProp) {
306
+ tagOverflowAttrs.push(showAllTagsLabelProp);
307
+ }
308
+ if (allTagsModalTitleProp) {
309
+ tagOverflowAttrs.push(allTagsModalTitleProp);
310
+ }
311
+ if (allTagsModalSearchLabelProp) {
312
+ tagOverflowAttrs.push(allTagsModalSearchLabelProp);
313
+ }
314
+ if (allTagsModalSearchPlaceholderTextProp) {
315
+ tagOverflowAttrs.push(allTagsModalSearchPlaceholderTextProp);
316
+ }
317
+ newChildren.push(
318
+ createMemberJSXElement(
319
+ pageHeaderLocalName,
320
+ 'TagOverflow',
321
+ tagOverflowAttrs,
322
+ []
323
+ )
324
+ );
325
+ }
326
+
327
+ // Update the PageHeader element
328
+ path.node.openingElement.attributes = rootAttributes;
329
+ path.node.children = newChildren;
330
+
331
+ // If PageHeader was self-closing, convert it to have opening/closing tags
332
+ if (path.node.openingElement.selfClosing) {
333
+ path.node.openingElement.selfClosing = false;
334
+ }
335
+ if (!path.node.closingElement) {
336
+ path.node.closingElement = j.jsxClosingElement(
337
+ j.jsxIdentifier(pageHeaderLocalName)
338
+ );
339
+ }
340
+ });
341
+
342
+ // Update imports if transformation occurred
343
+ if (dirtyFlag) {
344
+ root
345
+ .find(j.ImportDeclaration)
346
+ .filter((path) => path.node.source.value === '@carbon/ibm-products')
347
+ .forEach((path) => {
348
+ const specifiers = path.node.specifiers;
349
+ let hasPageHeader = false;
350
+
351
+ // Check if PageHeader is imported
352
+ specifiers.forEach((specifier) => {
353
+ if (
354
+ specifier.type === 'ImportSpecifier' &&
355
+ specifier.imported &&
356
+ specifier.imported.name === 'PageHeader'
357
+ ) {
358
+ hasPageHeader = true;
359
+ }
360
+ });
361
+
362
+ // If PageHeader is imported, update to preview version
363
+ if (hasPageHeader) {
364
+ const newSpecifiers = specifiers.map((specifier) => {
365
+ if (
366
+ specifier.type === 'ImportSpecifier' &&
367
+ specifier.imported &&
368
+ specifier.imported.name === 'PageHeader'
369
+ ) {
370
+ // Change to preview__PageHeader, preserving the local name (alias)
371
+ // If there's a local name (alias), preserve it; otherwise use 'PageHeader'
372
+ const localName = specifier.local
373
+ ? specifier.local.name
374
+ : 'PageHeader';
375
+ return j.importSpecifier(
376
+ j.identifier('preview__PageHeader'),
377
+ j.identifier(localName)
378
+ );
379
+ }
380
+ return specifier;
381
+ });
382
+
383
+ path.node.specifiers = newSpecifiers;
384
+ }
385
+ });
386
+ }
387
+
388
+ return root.toSource();
389
+ };
390
+
391
+ module.exports = transform;