@atlaskit/modal-dialog 12.0.2

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +2111 -0
  2. package/LICENSE +13 -0
  3. package/README.md +13 -0
  4. package/__perf__/default.tsx +42 -0
  5. package/__perf__/interactions.tsx +136 -0
  6. package/__perf__/scroll.tsx +98 -0
  7. package/codemods/12.0.0-lite-mode.ts +51 -0
  8. package/codemods/__tests__/12.0.0-lite-mode.test.ts +493 -0
  9. package/codemods/__tests__/handle-prop-spread.tsx +276 -0
  10. package/codemods/__tests__/inline-WidthNames-declaration.test.ts +260 -0
  11. package/codemods/__tests__/map-actions-prop.tsx +436 -0
  12. package/codemods/__tests__/map-body-from-props.test.ts +645 -0
  13. package/codemods/__tests__/map-container-from-props.test.ts +323 -0
  14. package/codemods/__tests__/map-footer-from-props.test.ts +544 -0
  15. package/codemods/__tests__/map-header-from-props.test.ts +559 -0
  16. package/codemods/__tests__/map-heading-prop.tsx +438 -0
  17. package/codemods/__tests__/remove-appearance-prop.test.ts +79 -0
  18. package/codemods/__tests__/remove-component-override-props.test.ts +153 -0
  19. package/codemods/__tests__/remove-is-chromeless.tsx +182 -0
  20. package/codemods/__tests__/rename-appearance-type.test.ts +52 -0
  21. package/codemods/__tests__/rename-inner-component-prop-types.test.ts +82 -0
  22. package/codemods/__tests__/rename-scrollBehavior-to-shouldScrollInViewport.test.ts +237 -0
  23. package/codemods/internal/constants.tsx +41 -0
  24. package/codemods/internal/utils.tsx +223 -0
  25. package/codemods/migrations/handle-prop-spread.tsx +51 -0
  26. package/codemods/migrations/inline-WidthNames-declaration.ts +92 -0
  27. package/codemods/migrations/map-actions-prop.tsx +430 -0
  28. package/codemods/migrations/map-body-from-props.ts +147 -0
  29. package/codemods/migrations/map-container-from-props.ts +72 -0
  30. package/codemods/migrations/map-footer-from-props.ts +107 -0
  31. package/codemods/migrations/map-header-from-props.ts +101 -0
  32. package/codemods/migrations/map-heading-prop.tsx +193 -0
  33. package/codemods/migrations/remove-appearance-prop.ts +27 -0
  34. package/codemods/migrations/remove-component-override-props.ts +84 -0
  35. package/codemods/migrations/remove-is-chromeless.tsx +42 -0
  36. package/codemods/migrations/rename-appearance-type.ts +9 -0
  37. package/codemods/migrations/rename-inner-component-prop-types.ts +28 -0
  38. package/codemods/migrations/rename-scrollBehavior-to-shouldScrollInViewport.ts +82 -0
  39. package/dist/cjs/hooks.js +22 -0
  40. package/dist/cjs/index.js +63 -0
  41. package/dist/cjs/internal/components/modal-dialog.js +155 -0
  42. package/dist/cjs/internal/components/positioner.js +89 -0
  43. package/dist/cjs/internal/components/scroll-container.js +138 -0
  44. package/dist/cjs/internal/constants.js +48 -0
  45. package/dist/cjs/internal/context.js +13 -0
  46. package/dist/cjs/internal/hooks/use-modal-stack.js +110 -0
  47. package/dist/cjs/internal/hooks/use-on-motion-finish.js +24 -0
  48. package/dist/cjs/internal/hooks/use-prevent-programmatic-scroll.js +55 -0
  49. package/dist/cjs/internal/hooks/use-scroll.js +20 -0
  50. package/dist/cjs/internal/utils.js +35 -0
  51. package/dist/cjs/modal-body.js +66 -0
  52. package/dist/cjs/modal-footer.js +40 -0
  53. package/dist/cjs/modal-header.js +43 -0
  54. package/dist/cjs/modal-title.js +108 -0
  55. package/dist/cjs/modal-transition.js +21 -0
  56. package/dist/cjs/modal-wrapper.js +126 -0
  57. package/dist/cjs/types.js +5 -0
  58. package/dist/cjs/version.json +5 -0
  59. package/dist/es2019/hooks.js +11 -0
  60. package/dist/es2019/index.js +7 -0
  61. package/dist/es2019/internal/components/modal-dialog.js +120 -0
  62. package/dist/es2019/internal/components/positioner.js +78 -0
  63. package/dist/es2019/internal/components/scroll-container.js +97 -0
  64. package/dist/es2019/internal/constants.js +27 -0
  65. package/dist/es2019/internal/context.js +3 -0
  66. package/dist/es2019/internal/hooks/use-modal-stack.js +85 -0
  67. package/dist/es2019/internal/hooks/use-on-motion-finish.js +17 -0
  68. package/dist/es2019/internal/hooks/use-prevent-programmatic-scroll.js +39 -0
  69. package/dist/es2019/internal/hooks/use-scroll.js +11 -0
  70. package/dist/es2019/internal/utils.js +22 -0
  71. package/dist/es2019/modal-body.js +50 -0
  72. package/dist/es2019/modal-footer.js +30 -0
  73. package/dist/es2019/modal-header.js +30 -0
  74. package/dist/es2019/modal-title.js +94 -0
  75. package/dist/es2019/modal-transition.js +10 -0
  76. package/dist/es2019/modal-wrapper.js +88 -0
  77. package/dist/es2019/types.js +1 -0
  78. package/dist/es2019/version.json +5 -0
  79. package/dist/esm/hooks.js +11 -0
  80. package/dist/esm/index.js +7 -0
  81. package/dist/esm/internal/components/modal-dialog.js +131 -0
  82. package/dist/esm/internal/components/positioner.js +76 -0
  83. package/dist/esm/internal/components/scroll-container.js +114 -0
  84. package/dist/esm/internal/constants.js +27 -0
  85. package/dist/esm/internal/context.js +3 -0
  86. package/dist/esm/internal/hooks/use-modal-stack.js +96 -0
  87. package/dist/esm/internal/hooks/use-on-motion-finish.js +16 -0
  88. package/dist/esm/internal/hooks/use-prevent-programmatic-scroll.js +44 -0
  89. package/dist/esm/internal/hooks/use-scroll.js +11 -0
  90. package/dist/esm/internal/utils.js +22 -0
  91. package/dist/esm/modal-body.js +49 -0
  92. package/dist/esm/modal-footer.js +29 -0
  93. package/dist/esm/modal-header.js +29 -0
  94. package/dist/esm/modal-title.js +93 -0
  95. package/dist/esm/modal-transition.js +10 -0
  96. package/dist/esm/modal-wrapper.js +96 -0
  97. package/dist/esm/types.js +1 -0
  98. package/dist/esm/version.json +5 -0
  99. package/dist/types/hooks.d.ts +1 -0
  100. package/dist/types/index.d.ts +8 -0
  101. package/dist/types/internal/components/modal-dialog.d.ts +3 -0
  102. package/dist/types/internal/components/positioner.d.ts +10 -0
  103. package/dist/types/internal/components/scroll-container.d.ts +20 -0
  104. package/dist/types/internal/constants.d.ts +25 -0
  105. package/dist/types/internal/context.d.ts +20 -0
  106. package/dist/types/internal/hooks/use-modal-stack.d.ts +13 -0
  107. package/dist/types/internal/hooks/use-on-motion-finish.d.ts +4 -0
  108. package/dist/types/internal/hooks/use-prevent-programmatic-scroll.d.ts +7 -0
  109. package/dist/types/internal/hooks/use-scroll.d.ts +1 -0
  110. package/dist/types/internal/utils.d.ts +3 -0
  111. package/dist/types/modal-body.d.ts +16 -0
  112. package/dist/types/modal-footer.d.ts +16 -0
  113. package/dist/types/modal-header.d.ts +16 -0
  114. package/dist/types/modal-title.d.ts +26 -0
  115. package/dist/types/modal-transition.d.ts +3 -0
  116. package/dist/types/modal-wrapper.d.ts +5 -0
  117. package/dist/types/types.d.ts +90 -0
  118. package/extract-react-types/modal-attributes.tsx +5 -0
  119. package/hooks/package.json +7 -0
  120. package/modal-body/package.json +7 -0
  121. package/modal-dialog/package.json +7 -0
  122. package/modal-footer/package.json +7 -0
  123. package/modal-header/package.json +7 -0
  124. package/modal-title/package.json +7 -0
  125. package/modal-transition/package.json +7 -0
  126. package/package.json +113 -0
  127. package/types/package.json +7 -0
@@ -0,0 +1,51 @@
1
+ import core, { ASTPath, Collection, JSXElement } from 'jscodeshift/src/core';
2
+
3
+ import {
4
+ addCommentToStartOfFile,
5
+ getDefaultSpecifier,
6
+ getDynamicImportName,
7
+ } from '@atlaskit/codemod-utils';
8
+
9
+ import { PACKAGE_NAME } from '../internal/constants';
10
+
11
+ const comment = `
12
+ This file is spreading props on the ModalDialog component, so we could not
13
+ automatically convert this usage to the new API.
14
+
15
+ The following props have been deprecated as part of moving to a compositional API:
16
+
17
+ - 'heading' prop has been replaced by ModalHeader and ModalTitle components.
18
+ - 'actions' prop has been replaced by ModalFooter component, with Button components from @atlaskit/button.
19
+ - 'scrollBehavior' prop has been replaced by 'shouldScrollInViewport', where "outside" from the previous prop maps to true in the new prop.
20
+ - 'isHeadingMultiline' prop has been replaced by 'isMultiline' prop on the ModalTitle component.
21
+ - 'appearance' prop has been moved to the ModalTitle component. To achieve the feature parity, pass the 'appearance' prop directly to ModalTitle and Button components inside ModalFooter.
22
+
23
+ Refer to the docs for the new API at https://atlassian.design/components/modal-dialog/examples
24
+ to complete the migration and use the new composable components.
25
+ `;
26
+
27
+ export const handlePropSpread = (
28
+ j: core.JSCodeshift,
29
+ source: Collection<Node>,
30
+ ) => {
31
+ const defaultSpecifierName = getDefaultSpecifier(j, source, PACKAGE_NAME);
32
+ const dynamicImportName = getDynamicImportName(j, source, PACKAGE_NAME);
33
+ const modalDialogComponentName = defaultSpecifierName || dynamicImportName;
34
+
35
+ if (!modalDialogComponentName) {
36
+ return;
37
+ }
38
+
39
+ if (
40
+ source
41
+ .findJSXElements(modalDialogComponentName)
42
+ .filter((element: ASTPath<JSXElement>) => {
43
+ return (
44
+ j(element).find(j.JSXOpeningElement).at(0).find(j.JSXSpreadAttribute)
45
+ .length > 0
46
+ );
47
+ }).length
48
+ ) {
49
+ addCommentToStartOfFile({ j, base: source, message: comment });
50
+ }
51
+ };
@@ -0,0 +1,92 @@
1
+ import core, {
2
+ Identifier,
3
+ TSCallSignatureDeclaration,
4
+ TSConstructSignatureDeclaration,
5
+ TSIndexSignature,
6
+ TSMethodSignature,
7
+ TSPropertySignature,
8
+ } from 'jscodeshift';
9
+ import { Collection } from 'jscodeshift/src/Collection';
10
+
11
+ import { getNamedSpecifier, removeImport } from '@atlaskit/codemod-utils';
12
+
13
+ import {
14
+ SHARED_VARIABLES_ENDPOINT,
15
+ WIDTH_NAMES_TYPE_NAME,
16
+ } from '../internal/constants';
17
+
18
+ export const inlineWidthNamesDeclaration = (
19
+ j: core.JSCodeshift,
20
+ source: Collection<Node>,
21
+ ) => {
22
+ const namedSpecifier = getNamedSpecifier(
23
+ j,
24
+ source,
25
+ SHARED_VARIABLES_ENDPOINT,
26
+ WIDTH_NAMES_TYPE_NAME,
27
+ );
28
+
29
+ if (!namedSpecifier) {
30
+ return;
31
+ }
32
+
33
+ source.find(j.TSTypeAliasDeclaration).forEach((declaration) => {
34
+ const annotation = declaration.value.typeAnnotation;
35
+
36
+ if (annotation.type === 'TSTypeLiteral') {
37
+ replaceTypeAnnotationForWidthNames(j, namedSpecifier, annotation.members);
38
+ }
39
+ });
40
+
41
+ source.find(j.TSInterfaceDeclaration).forEach((declaration) => {
42
+ replaceTypeAnnotationForWidthNames(
43
+ j,
44
+ namedSpecifier,
45
+ declaration.value.body.body,
46
+ );
47
+ });
48
+
49
+ /**
50
+ * Remove import from /shared-variables as it's no longer supported
51
+ * and this is the only use case of that entry point.
52
+ */
53
+ removeImport(j, source, SHARED_VARIABLES_ENDPOINT);
54
+ };
55
+
56
+ const replaceTypeAnnotationForWidthNames = (
57
+ j: core.JSCodeshift,
58
+ specifier: string,
59
+ typeAnnotations: (
60
+ | TSPropertySignature
61
+ | TSCallSignatureDeclaration
62
+ | TSConstructSignatureDeclaration
63
+ | TSIndexSignature
64
+ | TSMethodSignature
65
+ )[],
66
+ ) => {
67
+ const widthNames = ['small', 'medium', 'large', 'x-large'];
68
+
69
+ typeAnnotations
70
+ .filter((property) => property.type === 'TSPropertySignature')
71
+ .map((property) => property as TSPropertySignature)
72
+ .filter((property: TSPropertySignature) => {
73
+ const typeAnnotation = property.typeAnnotation?.typeAnnotation;
74
+
75
+ if (!typeAnnotation) {
76
+ return false;
77
+ }
78
+
79
+ return (
80
+ typeAnnotation.type === 'TSTypeReference' &&
81
+ typeAnnotation.typeName.type === 'Identifier' &&
82
+ (typeAnnotation.typeName as Identifier).name === specifier
83
+ );
84
+ })
85
+ .forEach((property: TSPropertySignature) => {
86
+ property.typeAnnotation = j.tsTypeAnnotation(
87
+ j.tsUnionType(
88
+ widthNames.map((name) => j.tsLiteralType(j.stringLiteral(name))),
89
+ ),
90
+ );
91
+ });
92
+ };
@@ -0,0 +1,430 @@
1
+ import core, {
2
+ ArrayExpression,
3
+ ASTPath,
4
+ Collection,
5
+ Identifier,
6
+ JSXAttribute,
7
+ JSXElement,
8
+ JSXExpressionContainer,
9
+ ObjectExpression,
10
+ Property,
11
+ VariableDeclaration,
12
+ VariableDeclarator,
13
+ } from 'jscodeshift/src/core';
14
+
15
+ import {
16
+ addCommentBefore,
17
+ addDynamicImport,
18
+ getDefaultSpecifier,
19
+ getDynamicImportName,
20
+ getJSXAttributesByName,
21
+ getSafeImportName,
22
+ tryCreateImport,
23
+ } from '@atlaskit/codemod-utils';
24
+
25
+ import {
26
+ ACTIONS_PROP_NAME,
27
+ APPEARANCE_PROP_NAME,
28
+ AUTOFOCUS_PROP_NAME,
29
+ BUTTON_COMPONENT_FALLBACK_NAME,
30
+ BUTTON_COMPONENT_NAME,
31
+ BUTTON_ENDPOINT,
32
+ MODAL_FOOTER_COMPONENT_FALLBACK_NAME,
33
+ MODAL_FOOTER_COMPONENT_NAME,
34
+ MODAL_FOOTER_ENDPOINT,
35
+ PACKAGE_NAME,
36
+ } from '../internal/constants';
37
+ import {
38
+ addToImport,
39
+ getAppearanceFromProp,
40
+ getComponentImportName,
41
+ getVariableDeclarationPathByName,
42
+ } from '../internal/utils';
43
+
44
+ const comment = `
45
+ In this codemod, we are moving the position of the primary button to the right-hand side of
46
+ modal footer to align with the design guidelines while we convert your usage of 'actions' prop.
47
+
48
+ However, we could not definitively determine if the 'appearance' prop has been included in the 'actions' prop in this file,
49
+ so in this case, we have converted the 'actions' prop into Button components without moving the position of the primary button.
50
+ To complete the migration and align with the design guidelines, please make the necessary changes manually.`;
51
+
52
+ export const mapActionsProp = (
53
+ j: core.JSCodeshift,
54
+ source: Collection<Node>,
55
+ ) => {
56
+ let isActionsPropPresent;
57
+ const defaultSpecifierName = getDefaultSpecifier(j, source, PACKAGE_NAME);
58
+ const buttonDefaultSpecifierName = getDefaultSpecifier(
59
+ j,
60
+ source,
61
+ BUTTON_ENDPOINT,
62
+ );
63
+
64
+ const dynamicImportName = getDynamicImportName(j, source, PACKAGE_NAME);
65
+ const modalDialogComponentName = defaultSpecifierName || dynamicImportName;
66
+
67
+ if (!modalDialogComponentName) {
68
+ return;
69
+ }
70
+ const modalFooterComponentName = getSafeImportName({
71
+ j,
72
+ base: source,
73
+ desiredName: MODAL_FOOTER_COMPONENT_NAME,
74
+ fallbackName: MODAL_FOOTER_COMPONENT_FALLBACK_NAME,
75
+ currentDefaultSpecifierName: null,
76
+ });
77
+ const buttonComponentName = getSafeImportName({
78
+ j,
79
+ base: source,
80
+ desiredName: BUTTON_COMPONENT_NAME,
81
+ fallbackName: BUTTON_COMPONENT_FALLBACK_NAME,
82
+ currentDefaultSpecifierName: buttonDefaultSpecifierName,
83
+ });
84
+
85
+ source
86
+ .findJSXElements(modalDialogComponentName)
87
+ .forEach((element: ASTPath<JSXElement>) => {
88
+ getJSXAttributesByName(j, element, ACTIONS_PROP_NAME).forEach(
89
+ (attribute: ASTPath<JSXAttribute>) => {
90
+ const actionsPropValue = (attribute.node
91
+ .value as JSXExpressionContainer)?.expression;
92
+
93
+ if (
94
+ !actionsPropValue ||
95
+ actionsPropValue.type === 'JSXEmptyExpression'
96
+ ) {
97
+ return;
98
+ }
99
+
100
+ isActionsPropPresent = true;
101
+
102
+ j(attribute).remove();
103
+
104
+ element.node.openingElement.selfClosing = false;
105
+ element.node.closingElement = j.jsxClosingElement(
106
+ j.jsxIdentifier(modalDialogComponentName),
107
+ );
108
+
109
+ const footerComponent = getFooterComponent(
110
+ j,
111
+ source,
112
+ element,
113
+ modalFooterComponentName,
114
+ buttonComponentName,
115
+ actionsPropValue,
116
+ );
117
+
118
+ if (element.node.children) {
119
+ element.node.children.push(footerComponent, j.jsxText('\n'));
120
+ } else {
121
+ element.node.children = [footerComponent, j.jsxText('\n')];
122
+ }
123
+ },
124
+ );
125
+ });
126
+
127
+ if (isActionsPropPresent) {
128
+ if (defaultSpecifierName) {
129
+ addToImport(j, source, [
130
+ getComponentImportName(
131
+ j,
132
+ modalFooterComponentName,
133
+ MODAL_FOOTER_COMPONENT_NAME,
134
+ ),
135
+ ]);
136
+
137
+ if (!buttonDefaultSpecifierName) {
138
+ tryCreateImport(j, source, PACKAGE_NAME, BUTTON_ENDPOINT);
139
+ addToImport(
140
+ j,
141
+ source,
142
+ [j.importDefaultSpecifier(j.identifier(buttonComponentName))],
143
+ BUTTON_ENDPOINT,
144
+ );
145
+ }
146
+ }
147
+ if (dynamicImportName) {
148
+ addDynamicImport(
149
+ j,
150
+ getVariableDeclarationPathByName(j, source, dynamicImportName),
151
+ modalFooterComponentName,
152
+ MODAL_FOOTER_ENDPOINT,
153
+ );
154
+ addDynamicImport(
155
+ j,
156
+ getVariableDeclarationPathByName(j, source, dynamicImportName),
157
+ buttonComponentName,
158
+ BUTTON_ENDPOINT,
159
+ );
160
+ }
161
+ }
162
+ };
163
+
164
+ const getAppearanceForButton = (
165
+ j: core.JSCodeshift,
166
+ element: ASTPath<JSXElement>,
167
+ isAppearanceSetInActions: boolean,
168
+ defaultValue: string,
169
+ ) => {
170
+ const modalDialogAppearance = getAppearanceFromProp(j, element);
171
+ const shouldUseModalDialogAppearance =
172
+ modalDialogAppearance && defaultValue === 'primary';
173
+
174
+ if (isAppearanceSetInActions && shouldUseModalDialogAppearance) {
175
+ return j.logicalExpression(
176
+ '||',
177
+ j.logicalExpression(
178
+ '||',
179
+ j.memberExpression(
180
+ j.identifier('props'),
181
+ j.identifier(APPEARANCE_PROP_NAME),
182
+ ),
183
+ // @ts-ignore
184
+ modalDialogAppearance,
185
+ ),
186
+ j.stringLiteral(defaultValue),
187
+ );
188
+ }
189
+
190
+ if (isAppearanceSetInActions && !shouldUseModalDialogAppearance) {
191
+ return j.logicalExpression(
192
+ '||',
193
+ j.memberExpression(
194
+ j.identifier('props'),
195
+ j.identifier(APPEARANCE_PROP_NAME),
196
+ ),
197
+ j.stringLiteral(defaultValue),
198
+ );
199
+ }
200
+
201
+ if (!isAppearanceSetInActions && shouldUseModalDialogAppearance) {
202
+ return j.logicalExpression(
203
+ '||',
204
+ // @ts-ignore
205
+ modalDialogAppearance,
206
+ j.stringLiteral(defaultValue),
207
+ );
208
+ }
209
+
210
+ return j.stringLiteral(defaultValue);
211
+ };
212
+
213
+ const getFooterComponent = (
214
+ j: core.JSCodeshift,
215
+ source: Collection<Node>,
216
+ element: ASTPath<JSXElement>,
217
+ footerComponentName: string,
218
+ buttonComponentName: string,
219
+ actions: any,
220
+ ) => {
221
+ const isAppearanceSetInActions = findAppearanceInActions(
222
+ j,
223
+ source,
224
+ element,
225
+ actions,
226
+ );
227
+
228
+ return j.jsxElement(
229
+ j.jsxOpeningElement(j.jsxIdentifier(footerComponentName)),
230
+ j.jsxClosingElement(j.jsxIdentifier(footerComponentName)),
231
+ [
232
+ j.jsxText('\n'),
233
+ j.jsxExpressionContainer(
234
+ /**
235
+ * We reverse the button order only if
236
+ * appearance is NOT set in actions.
237
+ */
238
+ isAppearanceSetInActions
239
+ ? mapActionsAndKeepOrder(j, element, buttonComponentName, actions)
240
+ : mapActionsAndReverseOrder(j, element, buttonComponentName, actions),
241
+ ),
242
+ j.jsxText('\n'),
243
+ ],
244
+ );
245
+ };
246
+
247
+ const mapActionsAndReverseOrder = (
248
+ j: core.JSCodeshift,
249
+ element: core.ASTPath<JSXElement>,
250
+ buttonComponentName: string,
251
+ actions: any,
252
+ ) => {
253
+ return j.callExpression(
254
+ j.memberExpression(
255
+ j.callExpression(j.memberExpression(actions, j.identifier('map')), [
256
+ j.arrowFunctionExpression(
257
+ [j.identifier('props'), j.identifier('index')],
258
+ j.jsxElement(
259
+ j.jsxOpeningElement(
260
+ j.jsxIdentifier(buttonComponentName),
261
+ getAttributesForReversedButtons(j, element),
262
+ ),
263
+ j.jsxClosingElement(j.jsxIdentifier(buttonComponentName)),
264
+ [
265
+ j.jsxExpressionContainer(
266
+ j.memberExpression(j.identifier('props'), j.identifier('text')),
267
+ ),
268
+ ],
269
+ ),
270
+ ),
271
+ ]),
272
+ j.identifier('reverse'),
273
+ ),
274
+ [],
275
+ );
276
+ };
277
+
278
+ const mapActionsAndKeepOrder = (
279
+ j: core.JSCodeshift,
280
+ element: core.ASTPath<JSXElement>,
281
+ buttonComponentName: string,
282
+ actions: any,
283
+ ) => {
284
+ return j.callExpression(j.memberExpression(actions, j.identifier('map')), [
285
+ j.arrowFunctionExpression(
286
+ [j.identifier('props'), j.identifier('index')],
287
+ j.jsxElement(
288
+ j.jsxOpeningElement(
289
+ j.jsxIdentifier(buttonComponentName),
290
+ getAttributesForButtons(j, element),
291
+ ),
292
+ j.jsxClosingElement(j.jsxIdentifier(buttonComponentName)),
293
+ [
294
+ j.jsxExpressionContainer(
295
+ j.memberExpression(j.identifier('props'), j.identifier('text')),
296
+ ),
297
+ ],
298
+ ),
299
+ ),
300
+ ]);
301
+ };
302
+
303
+ const getAttributesForReversedButtons = (
304
+ j: core.JSCodeshift,
305
+ element: core.ASTPath<JSXElement>,
306
+ ) => {
307
+ return [
308
+ j.jsxSpreadAttribute(j.identifier('props')),
309
+ j.jsxAttribute(
310
+ j.jsxIdentifier(AUTOFOCUS_PROP_NAME),
311
+ j.jsxExpressionContainer(
312
+ j.binaryExpression('===', j.identifier('index'), j.numericLiteral(0)),
313
+ ),
314
+ ),
315
+ j.jsxAttribute(
316
+ j.jsxIdentifier(APPEARANCE_PROP_NAME),
317
+ j.jsxExpressionContainer(
318
+ j.conditionalExpression(
319
+ j.binaryExpression('===', j.identifier('index'), j.numericLiteral(0)),
320
+ /**
321
+ * This function is called only when appearance is NOT found
322
+ * in actions – hence hardcoding it to 'false'.
323
+ */
324
+ getAppearanceForButton(j, element, false, 'primary'),
325
+ getAppearanceForButton(j, element, false, 'subtle'),
326
+ ),
327
+ ),
328
+ ),
329
+ ];
330
+ };
331
+
332
+ const getAttributesForButtons = (
333
+ j: core.JSCodeshift,
334
+ element: core.ASTPath<JSXElement>,
335
+ ) => {
336
+ return [
337
+ j.jsxSpreadAttribute(j.identifier('props')),
338
+ j.jsxAttribute(
339
+ j.jsxIdentifier(APPEARANCE_PROP_NAME),
340
+ j.jsxExpressionContainer(
341
+ j.conditionalExpression(
342
+ j.binaryExpression('===', j.identifier('index'), j.numericLiteral(0)),
343
+ /**
344
+ * This function is called only when appearance is found
345
+ * in actions – hence hardcoding it to 'true'.
346
+ */
347
+ getAppearanceForButton(j, element, true, 'primary'),
348
+ getAppearanceForButton(j, element, true, 'subtle'),
349
+ ),
350
+ ),
351
+ ),
352
+ ];
353
+ };
354
+
355
+ const findAppearanceInActions = (
356
+ j: core.JSCodeshift,
357
+ source: Collection<Node>,
358
+ element: core.ASTPath<JSXElement>,
359
+ actions: any,
360
+ ) => {
361
+ if (actions.type === 'Identifier') {
362
+ const actionPropVariableName = (actions as Identifier).name;
363
+
364
+ const actionsVariableDeclaration = getVariableDeclarationByName(
365
+ source,
366
+ j,
367
+ actionPropVariableName,
368
+ );
369
+
370
+ /**
371
+ * If there are multiple variables declared with the same name,
372
+ * we assume that it is and keep the button order the same.
373
+ */
374
+ if (actionsVariableDeclaration.length > 1) {
375
+ addCommentBefore(j, j(element), comment);
376
+
377
+ return true;
378
+ } else {
379
+ /**
380
+ * If actions prop is declared as variable, and we can find the declaration
381
+ * in the same file, we check if there's any appearance property set.
382
+ */
383
+ const actionPropVariableValue = (actionsVariableDeclaration as any)?.nodes()[0]
384
+ .declarations[0]?.init;
385
+
386
+ return checkAppearancePropertyExistence(actionPropVariableValue);
387
+ }
388
+ } else if (actions.type === 'ArrayExpression') {
389
+ /**
390
+ * If actions prop is declared inline, we check if there's any appearance property set.
391
+ */
392
+ return checkAppearancePropertyExistence(actions);
393
+ } else {
394
+ /**
395
+ * If we cannot definitively determine whether appearance is set in any of the actions,
396
+ * we assume that it is and keep the button order the same.
397
+ */
398
+ return true;
399
+ }
400
+ };
401
+
402
+ const checkAppearancePropertyExistence = (actions: any) => {
403
+ return (actions as ArrayExpression).elements.some((elem) => {
404
+ return (elem as ObjectExpression).properties.some((property) => {
405
+ return (
406
+ ((property as Property).key as Identifier).name === APPEARANCE_PROP_NAME
407
+ );
408
+ });
409
+ });
410
+ };
411
+
412
+ const getVariableDeclarationByName = (
413
+ source: core.Collection<Node>,
414
+ j: core.JSCodeshift,
415
+ variableName: string,
416
+ ) => {
417
+ return source
418
+ .find(j.VariableDeclaration)
419
+ .filter((variableDeclaration: ASTPath<VariableDeclaration>) => {
420
+ return variableDeclaration.node.declarations.some(
421
+ (variableDeclarator) => {
422
+ const variableDeclaratorTyped = variableDeclarator as VariableDeclarator;
423
+ return (
424
+ variableDeclaratorTyped.id.type === 'Identifier' &&
425
+ variableDeclaratorTyped.id.name === variableName
426
+ );
427
+ },
428
+ );
429
+ });
430
+ };