@carbon/upgrade 11.39.0-rc.0 → 11.39.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.
package/cli.js CHANGED
@@ -44455,6 +44455,34 @@ const upgrades = [
44455
44455
  });
44456
44456
  }
44457
44457
  },
44458
+ {
44459
+ name: "ibm-products-update-coachmark",
44460
+ description: "Rewrites old Coachmark to new composable Coachmark",
44461
+ migrate: async (options) => {
44462
+ const transform = path.default.join(TRANSFORM_DIR, "ibm-products-update-coachmark.js");
44463
+ const paths = Array.isArray(options.paths) && options.paths.length > 0 ? options.paths : await (0, import_out.default)([
44464
+ "**/*.js",
44465
+ "**/*.jsx",
44466
+ "**/*.ts",
44467
+ "**/*.tsx"
44468
+ ], {
44469
+ cwd: options.workspaceDir,
44470
+ ignore: [
44471
+ "**/es/**",
44472
+ "**/lib/**",
44473
+ "**/umd/**",
44474
+ "**/node_modules/**",
44475
+ "**/storybook-static/**"
44476
+ ]
44477
+ });
44478
+ await runCodemod(options, {
44479
+ dry: !options.write,
44480
+ transform,
44481
+ paths,
44482
+ verbose: options.verbose
44483
+ });
44484
+ }
44485
+ },
44458
44486
  {
44459
44487
  name: "rename-ibm-products-imports-to-preview",
44460
44488
  description: "Update imports after PDLC status integration",
@@ -44630,7 +44658,7 @@ const upgrades = [
44630
44658
  //#endregion
44631
44659
  //#region package.json
44632
44660
  var name = "@carbon/upgrade";
44633
- var version = "11.39.0-rc.0";
44661
+ var version = "11.39.0";
44634
44662
  //#endregion
44635
44663
  //#region ../../node_modules/y18n/build/index.cjs
44636
44664
  var require_build$3 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@carbon/upgrade",
3
3
  "description": "A tool for upgrading Carbon versions",
4
- "version": "11.39.0-rc.0",
4
+ "version": "11.39.0",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
7
7
  "carbon-upgrade": "./bin/carbon-upgrade.js"
@@ -60,5 +60,5 @@
60
60
  "@ibm/telemetry-js": "^1.5.0",
61
61
  "jscodeshift": "^17.0.0"
62
62
  },
63
- "gitHead": "3d219dfcc2a3da515016f7ce0e769d013c3a6c03"
63
+ "gitHead": "2d98f4ee9914fa44acba58f62599c3f5382acad0"
64
64
  }
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+ import {
3
+ Coachmark,
4
+ CoachmarkBeacon,
5
+ CoachmarkButton,
6
+ CoachmarkOverlayElements,
7
+ CoachmarkOverlayElement,
8
+ BEACON_KIND,
9
+ COACHMARK_OVERLAY_KIND,
10
+ } from '@carbon/ibm-products';
11
+ import { Crossroads } from '@carbon/react/icons';
12
+
13
+ // Example 1: Basic Coachmark with Beacon and dark theme
14
+ export const Example1 = () => (
15
+ <Coachmark
16
+ align="bottom"
17
+ closeIconDescription="Close"
18
+ positionTune={{ x: 0, y: 0 }}
19
+ target={
20
+ <CoachmarkBeacon label="Show information" kind={BEACON_KIND.DEFAULT} />
21
+ }
22
+ theme="light">
23
+ <CoachmarkOverlayElements closeButtonLabel="Done">
24
+ <CoachmarkOverlayElement
25
+ title="Hello World"
26
+ description="this is a description test"
27
+ />
28
+ </CoachmarkOverlayElements>
29
+ </Coachmark>
30
+ );
31
+
32
+ // Example 2: Coachmark with Button target and light theme
33
+ export const Example2 = () => (
34
+ <Coachmark
35
+ align="top"
36
+ closeIconDescription="Close"
37
+ positionTune={{ x: 10, y: 20 }}
38
+ target={
39
+ <CoachmarkButton
40
+ kind="tertiary"
41
+ size="md"
42
+ label="Show information"
43
+ renderIcon={Crossroads}>
44
+ Click Me
45
+ </CoachmarkButton>
46
+ }
47
+ theme="light">
48
+ <CoachmarkOverlayElements closeButtonLabel="Got it">
49
+ <CoachmarkOverlayElement
50
+ title="Welcome"
51
+ description="This is your dashboard"
52
+ />
53
+ </CoachmarkOverlayElements>
54
+ </Coachmark>
55
+ );
56
+
57
+ // Example 3: Floating Coachmark
58
+ export const Example3 = () => (
59
+ <Coachmark
60
+ align="bottom-left"
61
+ closeIconDescription="Dismiss"
62
+ positionTune={{ x: 5, y: 5 }}
63
+ overlayKind={COACHMARK_OVERLAY_KIND.FLOATING}
64
+ target={<CoachmarkBeacon label="Learn more" kind={BEACON_KIND.DEFAULT} />}
65
+ theme="dark">
66
+ <CoachmarkOverlayElements closeButtonLabel="Close">
67
+ <CoachmarkOverlayElement
68
+ title="New Feature"
69
+ description="Check out this new feature"
70
+ />
71
+ </CoachmarkOverlayElements>
72
+ </Coachmark>
73
+ );
@@ -0,0 +1,66 @@
1
+ import { Theme, Button } from "@carbon/react";
2
+ import React, { useState } from 'react';
3
+ import {
4
+ preview__Coachmark as Coachmark,
5
+ preview__CoachmarkBeacon as CoachmarkBeacon,
6
+ } from '@carbon/ibm-products';
7
+ import { Crossroads } from '@carbon/react/icons';
8
+
9
+ // Example 1: Basic Coachmark with Beacon and dark theme
10
+ export const Example1 = () => {
11
+ const [isOpen, setIsOpen] = useState(true);
12
+
13
+ const handleBeaconClick = () => {
14
+ setIsOpen(isOpen => !isOpen);
15
+ };
16
+
17
+ return (
18
+ <Theme theme="white"><Coachmark align="bottom" position={{ x: 0, y: 0 }} open={isOpen}><CoachmarkBeacon
19
+ label="Show information"
20
+ buttonProps={{
21
+ onClick: handleBeaconClick,
22
+ id: "CoachmarkBtn"
23
+ }} /><Coachmark.Content><Coachmark.Content.Header closeIconDescription="Close" /><Coachmark.Content.Body><h2>Hello World</h2><p>this is a description test</p><Button size="sm">Done</Button></Coachmark.Content.Body></Coachmark.Content></Coachmark></Theme>
24
+ );
25
+ };
26
+
27
+ // Example 2: Coachmark with Button target and light theme
28
+ export const Example2 = () => {
29
+ const [isOpen, setIsOpen] = useState(true);
30
+
31
+ const handleButtonClick = () => {
32
+ setIsOpen(isOpen => !isOpen);
33
+ };
34
+
35
+ return (
36
+ <Theme theme="white"><Coachmark align="top" position={{ x: 10, y: 20 }} open={isOpen}><Button
37
+ id="CoachmarkTriggerRefBtn"
38
+ onClick={handleButtonClick}
39
+ kind="tertiary"
40
+ size="md"
41
+ renderIcon={Crossroads}>Click Me
42
+ </Button><Coachmark.Content><Coachmark.Content.Header closeIconDescription="Close" /><Coachmark.Content.Body><h2>Welcome</h2><p>This is your dashboard</p><Button size="sm">Got it</Button></Coachmark.Content.Body></Coachmark.Content></Coachmark></Theme>
43
+ );
44
+ };
45
+
46
+ // Example 3: Floating Coachmark
47
+ export const Example3 = () => {
48
+ const [isOpen, setIsOpen] = useState(true);
49
+
50
+ const handleBeaconClick = () => {
51
+ setIsOpen(isOpen => !isOpen);
52
+ };
53
+
54
+ return (
55
+ <Theme theme="g90"><Coachmark
56
+ align="bottom-left"
57
+ position={{ x: 5, y: 5 }}
58
+ open={isOpen}
59
+ floating={true}><CoachmarkBeacon
60
+ label="Learn more"
61
+ buttonProps={{
62
+ onClick: handleBeaconClick,
63
+ id: "CoachmarkBtn"
64
+ }} /><Coachmark.Content><Coachmark.Content.Header closeIconDescription="Dismiss" /><Coachmark.Content.Body><h2>New Feature</h2><p>Check out this new feature</p><Button size="sm">Close</Button></Coachmark.Content.Body></Coachmark.Content></Coachmark></Theme>
65
+ );
66
+ };
@@ -0,0 +1,12 @@
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
+ 'use strict';
9
+
10
+ const { defineTest } = require('jscodeshift/dist/testUtils');
11
+
12
+ defineTest(__dirname, 'ibm-products-update-coachmark');
@@ -0,0 +1,504 @@
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 old Coachmark to new composable Coachmark
10
+ *
11
+ * Transforms:
12
+ *
13
+ * <Coachmark align='bottom' closeIconDescription='Close' positionTune={{ x: 0, y: 0 }}
14
+ * target={<CoachmarkBeacon label="Show information" kind={BEACON_KIND.DEFAULT} />} theme='dark'>
15
+ * <CoachmarkOverlayElements closeButtonLabel="Done">
16
+ * <CoachmarkOverlayElement title="Hello World" description="this is a description test" />
17
+ * </CoachmarkOverlayElements>
18
+ * </Coachmark>
19
+ *
20
+ * Into:
21
+ *
22
+ * <Theme theme={g90}>
23
+ * <Coachmark align='bottom' position={{ x: 0, y: 0 }}>
24
+ * <CoachmarkBeacon label="Show information" buttonProps={{ onClick: handleBeaconClick, id: 'CoachmarkBtn', ref: beaconButtonRef }} />
25
+ * <Coachmark.Content>
26
+ * <Coachmark.Content.Header closeIconDescription="Close" />
27
+ * <Coachmark.Content.Body>
28
+ * <h2>Hello World</h2>
29
+ * <p>this is a description test</p>
30
+ * <Button size="sm">Done</Button>
31
+ * </Coachmark.Content.Body>
32
+ * </Coachmark.Content>
33
+ * </Coachmark>
34
+ * </Theme>
35
+ */
36
+
37
+ 'use strict';
38
+
39
+ const transform = (fileInfo, api) => {
40
+ const j = api.jscodeshift;
41
+ const root = j(fileInfo.source);
42
+ let shouldImportTheme = false;
43
+ let shouldImportButton = false;
44
+ let shouldImportUseState = false;
45
+
46
+ // Helper to add imports to existing or create new import declarations
47
+ const ensureImport = (source, identifierName) => {
48
+ const existingImport = root
49
+ .find(j.ImportDeclaration, { source: { value: source } })
50
+ .filter((path) =>
51
+ path.node.specifiers.some((s) => s.imported?.name === identifierName)
52
+ );
53
+
54
+ if (existingImport.size() === 0) {
55
+ const reactImport = root.find(j.ImportDeclaration, {
56
+ source: { value: source },
57
+ });
58
+ if (reactImport.size() > 0) {
59
+ reactImport.forEach((path) =>
60
+ path.node.specifiers.push(
61
+ j.importSpecifier(j.identifier(identifierName))
62
+ )
63
+ );
64
+ } else {
65
+ root
66
+ .find(j.Program)
67
+ .get('body', 0)
68
+ .insertBefore(
69
+ j.importDeclaration(
70
+ [j.importSpecifier(j.identifier(identifierName))],
71
+ j.literal(source)
72
+ )
73
+ );
74
+ }
75
+ }
76
+ };
77
+
78
+ const createJSXElement = (name, attributes, children) => {
79
+ const openingElement = j.jsxOpeningElement(
80
+ j.jsxIdentifier(name),
81
+ attributes,
82
+ children.length === 0
83
+ );
84
+ return children.length === 0
85
+ ? j.jsxElement(openingElement, null, [])
86
+ : j.jsxElement(
87
+ openingElement,
88
+ j.jsxClosingElement(j.jsxIdentifier(name)),
89
+ children
90
+ );
91
+ };
92
+
93
+ const createMemberJSXElement = (object, property, attributes, children) => {
94
+ const memberExpression = j.jsxMemberExpression(
95
+ j.jsxIdentifier(object),
96
+ j.jsxIdentifier(property)
97
+ );
98
+ const openingElement = j.jsxOpeningElement(
99
+ memberExpression,
100
+ attributes,
101
+ children.length === 0
102
+ );
103
+ return children.length === 0
104
+ ? j.jsxElement(openingElement, null, [])
105
+ : j.jsxElement(
106
+ openingElement,
107
+ j.jsxClosingElement(memberExpression),
108
+ children
109
+ );
110
+ };
111
+
112
+ const createNestedMemberJSXElement = (
113
+ obj1,
114
+ obj2,
115
+ property,
116
+ attributes,
117
+ children
118
+ ) => {
119
+ const outerMember = j.jsxMemberExpression(
120
+ j.jsxMemberExpression(j.jsxIdentifier(obj1), j.jsxIdentifier(obj2)),
121
+ j.jsxIdentifier(property)
122
+ );
123
+ const openingElement = j.jsxOpeningElement(
124
+ outerMember,
125
+ attributes,
126
+ children.length === 0
127
+ );
128
+ return children.length === 0
129
+ ? j.jsxElement(openingElement, null, [])
130
+ : j.jsxElement(
131
+ openingElement,
132
+ j.jsxClosingElement(outerMember),
133
+ children
134
+ );
135
+ };
136
+
137
+ // Transform target elements (CoachmarkBeacon or CoachmarkButton)
138
+ const processTargetElement = (targetValue) => {
139
+ if (
140
+ targetValue.type !== 'JSXExpressionContainer' ||
141
+ targetValue.expression.type !== 'JSXElement'
142
+ )
143
+ return null;
144
+
145
+ const expression = targetValue.expression;
146
+ const elementName = expression.openingElement.name.name;
147
+
148
+ // Transform CoachmarkBeacon with buttonProps
149
+ if (elementName === 'CoachmarkBeacon') {
150
+ shouldImportUseState = true;
151
+ const label = expression.openingElement.attributes.find(
152
+ (a) => a.name?.name === 'label'
153
+ )?.value;
154
+ const newAttributes = [
155
+ j.jsxAttribute(
156
+ j.jsxIdentifier('buttonProps'),
157
+ j.jsxExpressionContainer(
158
+ j.objectExpression([
159
+ j.property(
160
+ 'init',
161
+ j.identifier('onClick'),
162
+ j.identifier('handleBeaconClick')
163
+ ),
164
+ j.property('init', j.identifier('id'), j.literal('CoachmarkBtn')),
165
+ ])
166
+ )
167
+ ),
168
+ ];
169
+ if (label)
170
+ newAttributes.unshift(j.jsxAttribute(j.jsxIdentifier('label'), label));
171
+ return createJSXElement('CoachmarkBeacon', newAttributes, []);
172
+ }
173
+
174
+ // Transform CoachmarkButton to regular Button
175
+ if (elementName === 'CoachmarkButton') {
176
+ shouldImportButton = true;
177
+ shouldImportUseState = true;
178
+ const attrs = expression.openingElement.attributes;
179
+ const buttonAttributes = [
180
+ j.jsxAttribute(
181
+ j.jsxIdentifier('id'),
182
+ j.literal('CoachmarkTriggerRefBtn')
183
+ ),
184
+ j.jsxAttribute(
185
+ j.jsxIdentifier('onClick'),
186
+ j.jsxExpressionContainer(j.identifier('handleButtonClick'))
187
+ ),
188
+ ];
189
+ ['kind', 'size', 'renderIcon'].forEach((name) => {
190
+ const attr = attrs.find((a) => a.name?.name === name);
191
+ if (attr)
192
+ buttonAttributes.push(
193
+ j.jsxAttribute(j.jsxIdentifier(name), attr.value)
194
+ );
195
+ });
196
+ return createJSXElement(
197
+ 'Button',
198
+ buttonAttributes,
199
+ expression.children || []
200
+ );
201
+ }
202
+ return null;
203
+ };
204
+
205
+ // Transform Coachmark components
206
+ root
207
+ .find(j.JSXElement, { openingElement: { name: { name: 'Coachmark' } } })
208
+ .forEach((path) => {
209
+ const attributes = path.node.openingElement.attributes;
210
+ const newAttributes = [];
211
+ let themeValue = null,
212
+ closeIconDescription = null,
213
+ targetElement = null,
214
+ overlayKind = null;
215
+ let closeButtonLabel = null,
216
+ overlayElements = [];
217
+
218
+ // Process and transform attributes
219
+ attributes.forEach((attr) => {
220
+ if (attr.type !== 'JSXAttribute') {
221
+ newAttributes.push(attr);
222
+ return;
223
+ }
224
+ const attrName = attr.name.name;
225
+
226
+ if (attrName === 'theme') {
227
+ if (attr.value.type === 'Literal') {
228
+ themeValue =
229
+ attr.value.value === 'dark'
230
+ ? 'g90'
231
+ : attr.value.value === 'light'
232
+ ? 'white'
233
+ : null;
234
+ }
235
+ shouldImportTheme = true;
236
+ } else if (attrName === 'positionTune') {
237
+ attr.name.name = 'position';
238
+ newAttributes.push(attr);
239
+ } else if (attrName === 'closeIconDescription') {
240
+ closeIconDescription = attr.value;
241
+ } else if (attrName === 'target') {
242
+ targetElement = attr.value;
243
+ } else if (attrName === 'overlayKind') {
244
+ overlayKind = attr.value;
245
+ } else {
246
+ newAttributes.push(attr);
247
+ }
248
+ });
249
+
250
+ newAttributes.push(
251
+ j.jsxAttribute(
252
+ j.jsxIdentifier('open'),
253
+ j.jsxExpressionContainer(j.identifier('isOpen'))
254
+ )
255
+ );
256
+ if (overlayKind) {
257
+ newAttributes.push(
258
+ j.jsxAttribute(
259
+ j.jsxIdentifier('floating'),
260
+ j.jsxExpressionContainer(j.literal(true))
261
+ )
262
+ );
263
+ }
264
+
265
+ const newChildren = [];
266
+ if (targetElement) {
267
+ const targetChild = processTargetElement(targetElement);
268
+ if (targetChild) newChildren.push(targetChild);
269
+ }
270
+
271
+ if (path.node.children) {
272
+ path.node.children.forEach((child) => {
273
+ if (
274
+ child.type === 'JSXElement' &&
275
+ child.openingElement.name.name === 'CoachmarkOverlayElements'
276
+ ) {
277
+ child.openingElement.attributes.forEach((attr) => {
278
+ if (attr.name?.name === 'closeButtonLabel')
279
+ closeButtonLabel = attr.value;
280
+ });
281
+ child.children.forEach((overlayChild) => {
282
+ if (
283
+ overlayChild.type === 'JSXElement' &&
284
+ overlayChild.openingElement.name.name ===
285
+ 'CoachmarkOverlayElement'
286
+ ) {
287
+ overlayElements.push(overlayChild);
288
+ }
289
+ });
290
+ }
291
+ });
292
+ }
293
+
294
+ // Build content body from overlay elements
295
+ const contentBodyChildren = [];
296
+ overlayElements.forEach((element) => {
297
+ const attrs = element.openingElement.attributes;
298
+ const title = attrs.find((a) => a.name?.name === 'title')?.value;
299
+ const description = attrs.find(
300
+ (a) => a.name?.name === 'description'
301
+ )?.value;
302
+
303
+ if (title) {
304
+ const titleValue = title.type === 'Literal' ? title.value : title;
305
+ contentBodyChildren.push(
306
+ j.jsxElement(
307
+ j.jsxOpeningElement(j.jsxIdentifier('h2'), []),
308
+ j.jsxClosingElement(j.jsxIdentifier('h2')),
309
+ [j.jsxText(typeof titleValue === 'string' ? titleValue : '')]
310
+ )
311
+ );
312
+ }
313
+ if (description) {
314
+ const descValue =
315
+ description.type === 'Literal' ? description.value : description;
316
+ contentBodyChildren.push(
317
+ j.jsxElement(
318
+ j.jsxOpeningElement(j.jsxIdentifier('p'), []),
319
+ j.jsxClosingElement(j.jsxIdentifier('p')),
320
+ [j.jsxText(typeof descValue === 'string' ? descValue : '')]
321
+ )
322
+ );
323
+ }
324
+ });
325
+
326
+ if (closeButtonLabel) {
327
+ shouldImportButton = true;
328
+ const buttonLabel =
329
+ closeButtonLabel.type === 'Literal' ? closeButtonLabel.value : 'Done';
330
+ contentBodyChildren.push(
331
+ j.jsxElement(
332
+ j.jsxOpeningElement(j.jsxIdentifier('Button'), [
333
+ j.jsxAttribute(j.jsxIdentifier('size'), j.literal('sm')),
334
+ ]),
335
+ j.jsxClosingElement(j.jsxIdentifier('Button')),
336
+ [j.jsxText(buttonLabel)]
337
+ )
338
+ );
339
+ }
340
+
341
+ const contentBody = createNestedMemberJSXElement(
342
+ 'Coachmark',
343
+ 'Content',
344
+ 'Body',
345
+ [],
346
+ contentBodyChildren
347
+ );
348
+ const headerAttributes = closeIconDescription
349
+ ? [
350
+ j.jsxAttribute(
351
+ j.jsxIdentifier('closeIconDescription'),
352
+ closeIconDescription
353
+ ),
354
+ ]
355
+ : [];
356
+ const contentHeader = createNestedMemberJSXElement(
357
+ 'Coachmark',
358
+ 'Content',
359
+ 'Header',
360
+ headerAttributes,
361
+ []
362
+ );
363
+ const content = createMemberJSXElement(
364
+ 'Coachmark',
365
+ 'Content',
366
+ [],
367
+ [contentHeader, contentBody]
368
+ );
369
+
370
+ newChildren.push(content);
371
+ path.node.openingElement.attributes = newAttributes;
372
+ path.node.children = newChildren;
373
+
374
+ if (themeValue) {
375
+ const themeElement = j.jsxElement(
376
+ j.jsxOpeningElement(j.jsxIdentifier('Theme'), [
377
+ j.jsxAttribute(j.jsxIdentifier('theme'), j.literal(themeValue)),
378
+ ]),
379
+ j.jsxClosingElement(j.jsxIdentifier('Theme')),
380
+ [
381
+ j.jsxElement(
382
+ path.node.openingElement,
383
+ path.node.closingElement,
384
+ path.node.children
385
+ ),
386
+ ]
387
+ );
388
+ j(path).replaceWith(themeElement);
389
+ }
390
+ });
391
+
392
+ // Update @carbon/ibm-products imports to preview versions
393
+ root
394
+ .find(j.ImportDeclaration)
395
+ .filter((path) => path.node.source.value === '@carbon/ibm-products')
396
+ .forEach((path) => {
397
+ const removedImports = [
398
+ 'CoachmarkOverlayElements',
399
+ 'CoachmarkOverlayElement',
400
+ 'CoachmarkButton',
401
+ 'BEACON_KIND',
402
+ 'COACHMARK_OVERLAY_KIND',
403
+ ];
404
+ const newSpecifiers = [];
405
+
406
+ path.node.specifiers.forEach((specifier) => {
407
+ if (!specifier.imported) {
408
+ newSpecifiers.push(specifier);
409
+ return;
410
+ }
411
+
412
+ const importedName = specifier.imported.name;
413
+ if (removedImports.includes(importedName)) return;
414
+
415
+ if (importedName === 'Coachmark') {
416
+ newSpecifiers.push(
417
+ j.importSpecifier(
418
+ j.identifier('preview__Coachmark'),
419
+ j.identifier('Coachmark')
420
+ )
421
+ );
422
+ } else if (importedName === 'CoachmarkBeacon') {
423
+ newSpecifiers.push(
424
+ j.importSpecifier(
425
+ j.identifier('preview__CoachmarkBeacon'),
426
+ j.identifier('CoachmarkBeacon')
427
+ )
428
+ );
429
+ } else {
430
+ newSpecifiers.push(specifier);
431
+ }
432
+ });
433
+
434
+ path.node.specifiers = newSpecifiers;
435
+ });
436
+
437
+ // Add required imports
438
+ if (shouldImportTheme) ensureImport('@carbon/react', 'Theme');
439
+ if (shouldImportButton) ensureImport('@carbon/react', 'Button');
440
+ if (shouldImportUseState) ensureImport('react', 'useState');
441
+
442
+ // Inject state management into component functions
443
+ root.find(j.VariableDeclarator).forEach((path) => {
444
+ const init = path.node.init;
445
+ if (
446
+ !init ||
447
+ init.type !== 'ArrowFunctionExpression' ||
448
+ init.body.type !== 'JSXElement'
449
+ )
450
+ return;
451
+
452
+ const jsx = init.body;
453
+ const containsTheme = jsx.openingElement?.name?.name === 'Theme';
454
+ const containsCoachmark = jsx.openingElement?.name?.name === 'Coachmark';
455
+ if (!containsTheme && !containsCoachmark) return;
456
+
457
+ const jsxSource = j(jsx).toSource();
458
+ const usesBeaconHandler = jsxSource.includes('handleBeaconClick');
459
+ const usesButtonHandler = jsxSource.includes('handleButtonClick');
460
+ if (!usesBeaconHandler && !usesButtonHandler) return;
461
+
462
+ shouldImportUseState = true;
463
+ const bodyStatements = [
464
+ j.variableDeclaration('const', [
465
+ j.variableDeclarator(
466
+ j.arrayPattern([j.identifier('isOpen'), j.identifier('setIsOpen')]),
467
+ j.callExpression(j.identifier('useState'), [j.literal(true)])
468
+ ),
469
+ ]),
470
+ ];
471
+
472
+ const createHandler = (name) =>
473
+ j.variableDeclaration('const', [
474
+ j.variableDeclarator(
475
+ j.identifier(name),
476
+ j.arrowFunctionExpression(
477
+ [],
478
+ j.blockStatement([
479
+ j.expressionStatement(
480
+ j.callExpression(j.identifier('setIsOpen'), [
481
+ j.arrowFunctionExpression(
482
+ [j.identifier('isOpen')],
483
+ j.unaryExpression('!', j.identifier('isOpen'))
484
+ ),
485
+ ])
486
+ ),
487
+ ])
488
+ )
489
+ ),
490
+ ]);
491
+
492
+ if (usesBeaconHandler)
493
+ bodyStatements.push(createHandler('handleBeaconClick'));
494
+ if (usesButtonHandler)
495
+ bodyStatements.push(createHandler('handleButtonClick'));
496
+ bodyStatements.push(j.returnStatement(jsx));
497
+
498
+ init.body = j.blockStatement(bodyStatements);
499
+ });
500
+
501
+ return root.toSource();
502
+ };
503
+
504
+ module.exports = transform;