@pantheon-systems/pds-toolkit-react 2.0.0-alpha.37 → 2.0.0-alpha.39

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 (115) hide show
  1. package/codemods/v1-to-v2/mappings.json +217 -28
  2. package/codemods/v1-to-v2/transforms/__testfixtures__/component-renames/basic.input.tsx +33 -0
  3. package/codemods/v1-to-v2/transforms/__testfixtures__/component-renames/basic.output.tsx +33 -0
  4. package/codemods/v1-to-v2/transforms/__testfixtures__/component-renames/only.input.tsx +8 -0
  5. package/codemods/v1-to-v2/transforms/__testfixtures__/component-renames/only.output.tsx +8 -0
  6. package/codemods/v1-to-v2/transforms/__testfixtures__/css-class-removals/basic.input.tsx +31 -0
  7. package/codemods/v1-to-v2/transforms/__testfixtures__/css-class-removals/basic.output.tsx +31 -0
  8. package/codemods/v1-to-v2/transforms/__testfixtures__/css-class-renames/basic.input.tsx +31 -0
  9. package/codemods/v1-to-v2/transforms/__testfixtures__/css-class-renames/basic.output.tsx +31 -0
  10. package/codemods/v1-to-v2/transforms/__testfixtures__/icon-name-updates/basic.input.tsx +35 -0
  11. package/codemods/v1-to-v2/transforms/__testfixtures__/icon-name-updates/basic.output.tsx +35 -0
  12. package/codemods/v1-to-v2/transforms/__testfixtures__/icon-name-updates/only.input.tsx +8 -0
  13. package/codemods/v1-to-v2/transforms/__testfixtures__/icon-name-updates/only.output.tsx +8 -0
  14. package/codemods/v1-to-v2/transforms/__testfixtures__/prop-renames/basic.input.tsx +28 -0
  15. package/codemods/v1-to-v2/transforms/__testfixtures__/prop-renames/basic.output.tsx +28 -0
  16. package/codemods/v1-to-v2/transforms/__testfixtures__/prop-renames/only.input.tsx +8 -0
  17. package/codemods/v1-to-v2/transforms/__testfixtures__/prop-renames/only.output.tsx +8 -0
  18. package/codemods/v1-to-v2/transforms/__testfixtures__/prop-value-standardization/basic.output.tsx +10 -10
  19. package/codemods/v1-to-v2/transforms/__testfixtures__/prop-value-standardization/only.input.tsx +21 -0
  20. package/codemods/v1-to-v2/transforms/__testfixtures__/prop-value-standardization/only.output.tsx +21 -0
  21. package/codemods/v1-to-v2/transforms/__testfixtures__/removed-prop-values/basic.input.tsx +60 -0
  22. package/codemods/v1-to-v2/transforms/__testfixtures__/removed-prop-values/basic.output.tsx +51 -0
  23. package/codemods/v1-to-v2/transforms/__testfixtures__/removed-prop-values/only.input.tsx +8 -0
  24. package/codemods/v1-to-v2/transforms/__testfixtures__/removed-prop-values/only.output.tsx +8 -0
  25. package/codemods/v1-to-v2/transforms/__tests__/component-renames.test.js +15 -0
  26. package/codemods/v1-to-v2/transforms/__tests__/css-class-removals.test.js +6 -0
  27. package/codemods/v1-to-v2/transforms/__tests__/css-class-renames.test.js +6 -0
  28. package/codemods/v1-to-v2/transforms/__tests__/icon-name-updates.test.js +15 -0
  29. package/codemods/v1-to-v2/transforms/__tests__/prop-renames.test.js +15 -0
  30. package/codemods/v1-to-v2/transforms/__tests__/prop-value-standardization.test.js +10 -0
  31. package/codemods/v1-to-v2/transforms/__tests__/removed-prop-values.test.js +15 -0
  32. package/codemods/v1-to-v2/transforms/component-renames.js +166 -0
  33. package/codemods/v1-to-v2/transforms/css-class-removals.js +192 -0
  34. package/codemods/v1-to-v2/transforms/css-class-renames.js +114 -0
  35. package/codemods/v1-to-v2/transforms/icon-name-updates.js +124 -0
  36. package/codemods/v1-to-v2/transforms/prop-renames.js +109 -0
  37. package/codemods/v1-to-v2/transforms/prop-value-standardization.js +33 -2
  38. package/codemods/v1-to-v2/transforms/removed-prop-values.js +206 -0
  39. package/dist/components/Dropdown/Dropdown.d.ts +46 -0
  40. package/dist/components/SiteFooter/SiteFooter.d.ts +4 -1
  41. package/dist/components/Tooltip/Tooltip.d.ts +1 -1
  42. package/dist/components/UtilityBar/UtilityBar.d.ts +2 -3
  43. package/dist/components/buttons/ButtonLink/ButtonLink.d.ts +1 -3
  44. package/dist/components/buttons/CloseButton/CloseButton.d.ts +2 -2
  45. package/dist/components/buttons/FilterButton/FilterButton.d.ts +82 -0
  46. package/dist/components/buttons/SegmentedButton/SegmentedButton.d.ts +7 -1
  47. package/dist/components/cards/ActionCard/ActionCard.d.ts +2 -2
  48. package/dist/components/cards/Card/Card.d.ts +2 -2
  49. package/dist/components/icons/Icon/custom-icons.d.ts +1 -1
  50. package/dist/components/icons/Icon/generated-icon-data.d.ts +107 -30
  51. package/dist/components/navigation/Navbar/Navbar.d.ts +10 -1
  52. package/dist/components/panels/Panel/Panel.d.ts +2 -2
  53. package/dist/components/progress-indicators/ProgressBar/ProgressBar.d.ts +1 -1
  54. package/dist/components/progress-indicators/ProgressRing/ProgressRing.d.ts +10 -1
  55. package/dist/css/component-css/pds-action-card.css +1 -1
  56. package/dist/css/component-css/pds-button-link.css +1 -1
  57. package/dist/css/component-css/pds-button.css +1 -1
  58. package/dist/css/component-css/pds-card.css +1 -1
  59. package/dist/css/component-css/pds-checkbox.css +1 -1
  60. package/dist/css/component-css/pds-close-button.css +1 -1
  61. package/dist/css/component-css/pds-dropdown.css +2 -2
  62. package/dist/css/component-css/pds-filter-button.css +1 -0
  63. package/dist/css/component-css/pds-icon.css +1 -1
  64. package/dist/css/component-css/pds-index.css +23 -23
  65. package/dist/css/component-css/pds-panel.css +1 -1
  66. package/dist/css/component-css/pds-progress-ring.css +1 -1
  67. package/dist/css/component-css/pds-radio-group.css +1 -1
  68. package/dist/css/component-css/pds-segmented-button.css +1 -1
  69. package/dist/css/component-css/pds-side-nav-global.css +1 -1
  70. package/dist/css/component-css/pds-site-footer.css +1 -1
  71. package/dist/css/component-css/pds-site-search.css +1 -1
  72. package/dist/css/component-css/pds-skeleton.css +1 -1
  73. package/dist/css/component-css/pds-switch.css +2 -2
  74. package/dist/css/component-css/pds-utility-bar.css +1 -1
  75. package/dist/css/component-css/pds-vertical-empty-state.css +1 -1
  76. package/dist/css/design-tokens/variables.dark.css +6 -2
  77. package/dist/css/design-tokens/variables.global.css +0 -1
  78. package/dist/css/design-tokens/variables.light.css +4 -0
  79. package/dist/css/layout-css/pds-app-layout.css +5 -2
  80. package/dist/css/layout-css/pds-index.css +5 -2
  81. package/dist/css/layout-css/pds-sidebar-layout.css +1 -1
  82. package/dist/css/pds-components.css +23 -23
  83. package/dist/css/pds-core.css +2 -2
  84. package/dist/css/pds-layouts.css +5 -2
  85. package/dist/index.css +1 -1
  86. package/dist/index.d.ts +3 -0
  87. package/dist/index.js +1340 -1032
  88. package/dist/index.js.map +1 -1
  89. package/dist/index.source.d.ts +3 -0
  90. package/dist/layouts/AppLayout/AppLayout.d.ts +22 -2
  91. package/dist/layouts/SidebarLayout/SidebarLayout.d.ts +4 -3
  92. package/dist/layouts/ThreeItemLayout/ThreeItemLayout.d.ts +3 -3
  93. package/dist/layouts/TwoItemLayout/TwoItemLayout.d.ts +3 -3
  94. package/dist/libs/types/custom-types.d.ts +2 -2
  95. package/dist/libs/types/layout-types.d.ts +2 -1
  96. package/dist/svg/circleHalfStroke.svg +3 -0
  97. package/dist/svg/claude.svg +3 -0
  98. package/dist/svg/clock.svg +3 -0
  99. package/dist/svg/diagramVenn.svg +3 -0
  100. package/dist/svg/floppyDisk.svg +3 -0
  101. package/dist/svg/grid.svg +2 -2
  102. package/dist/svg/grid2.svg +3 -0
  103. package/dist/svg/octagonExclamation.svg +3 -0
  104. package/dist/svg/openai.svg +3 -0
  105. package/dist/svg/slider.svg +3 -0
  106. package/dist/svg/squareDashed.svg +3 -0
  107. package/dist/svg/tableColumns.svg +3 -0
  108. package/dist/utilities/FlexContainer/FlexContainer.d.ts +5 -6
  109. package/dist/utilities/grid/Grid.d.ts +58 -0
  110. package/dist/utilities/grid/GridItem.d.ts +57 -0
  111. package/package.json +5 -7
  112. /package/dist/svg/{arrowDownLeftAndArrowUpRightToCenter.svg → collapseToCenter.svg} +0 -0
  113. /package/dist/svg/{linkSimple.svg → link.svg} +0 -0
  114. /package/dist/svg/{arrowsUpDownLeftRight.svg → move.svg} +0 -0
  115. /package/dist/svg/{linkSimpleSlash.svg → unlink.svg} +0 -0
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Codemod: css-class-removals
3
+ *
4
+ * Handles removed PDS CSS utility classes in JSX className attributes.
5
+ * Driven entirely by cssClasses.removals in mappings.json.
6
+ *
7
+ * Three kinds of change:
8
+ *
9
+ * 1. Direct renames — replacement is another class name:
10
+ * pds-button--navbar → pds-button--subtle
11
+ * pds-ts-9xl → pds-ts-8xl
12
+ * Replaced automatically.
13
+ *
14
+ * 2. Safe removals — replacement text says "no class needed":
15
+ * pds-typography--product → removed (product context is now the default)
16
+ * Removed automatically.
17
+ *
18
+ * 3. Flag only — replacement requires manual styling work with design tokens:
19
+ * pds-color-bg-brand, pds-color-fg-brand, pds-lead-text*, pds-quote-text*
20
+ * Left in place; a warning is printed so the developer can find and fix
21
+ * these manually. Removing them silently would break visible styles with
22
+ * no clear path forward.
23
+ *
24
+ * Usage:
25
+ * npx jscodeshift -t codemods/v1-to-v2/transforms/css-class-removals.js \
26
+ * --extensions=tsx,ts,jsx,js \
27
+ * src/
28
+ *
29
+ * No --only flag — CSS utility classes apply to any element globally.
30
+ */
31
+
32
+ const path = require('path');
33
+ const mappings = require(path.join(__dirname, '../mappings.json'));
34
+
35
+ // Extract the first pds-* class name from a replacement string, if present.
36
+ // Strips CSS custom property references (var(--pds-*)) first so that token
37
+ // names mentioned in prose explanations aren't mistaken for class names.
38
+ function extractReplacementClass(replacement) {
39
+ if (!replacement) return null;
40
+ const cleaned = replacement.replace(/var\(--[^)]+\)/g, '');
41
+ const match = cleaned.match(/\bpds-[a-z0-9-]+/);
42
+ return match ? match[0] : null;
43
+ }
44
+
45
+ // Classify each removal entry into one of three buckets.
46
+ function buildLookups() {
47
+ // { oldClass: newClass } — rename automatically
48
+ const renames = {};
49
+ // Set of class names to remove unconditionally
50
+ const removals = new Set();
51
+ // Set of class names to flag with a warning (no safe auto-fix)
52
+ const flags = new Set();
53
+
54
+ for (const entry of mappings.cssClasses.removals) {
55
+ const { old: oldClass, replacement } = entry;
56
+ if (!oldClass) continue;
57
+
58
+ const replacementClass = extractReplacementClass(replacement);
59
+ if (replacementClass) {
60
+ renames[oldClass] = replacementClass;
61
+ } else if (
62
+ replacement &&
63
+ /no class needed|no replacement/i.test(replacement)
64
+ ) {
65
+ removals.add(oldClass);
66
+ } else {
67
+ flags.add(oldClass);
68
+ }
69
+ }
70
+
71
+ return { renames, removals, flags };
72
+ }
73
+
74
+ const { renames: RENAMES, removals: REMOVALS, flags: FLAGS } = buildLookups();
75
+
76
+ // Process a space-separated class string.
77
+ // Returns { result, changed, flagged[] }
78
+ function processClasses(classString, filePath) {
79
+ let changed = false;
80
+ const flagged = [];
81
+
82
+ const result = classString
83
+ .split(/(\s+)/)
84
+ .map((token) => {
85
+ if (/^\s+$/.test(token) || token === '') return token;
86
+
87
+ if (RENAMES[token]) {
88
+ changed = true;
89
+ return RENAMES[token];
90
+ }
91
+ if (REMOVALS.has(token)) {
92
+ changed = true;
93
+ return null; // will be filtered out
94
+ }
95
+ if (FLAGS.has(token)) {
96
+ flagged.push(token);
97
+ }
98
+ return token;
99
+ })
100
+ .filter((token) => token !== null)
101
+ .join('');
102
+
103
+ // Normalise any double-spaces left by removed tokens
104
+ const normalised = result.replace(/ +/g, ' ').trim();
105
+ const didChange = changed || normalised !== classString.trim();
106
+
107
+ return { result: normalised, changed: didChange, flagged };
108
+ }
109
+
110
+ module.exports = function transform(file, api) {
111
+ const j = api.jscodeshift;
112
+ const root = j(file.source);
113
+
114
+ const allFlagged = []; // { file, class } pairs for the summary warning
115
+ let changed = false;
116
+
117
+ root
118
+ .find(j.JSXAttribute, { name: { name: 'className' } })
119
+ .forEach((nodePath) => {
120
+ const { value } = nodePath.node;
121
+ if (!value) return;
122
+
123
+ // className="pds-button--navbar foo"
124
+ if (value.type === 'StringLiteral') {
125
+ const {
126
+ result,
127
+ changed: c,
128
+ flagged,
129
+ } = processClasses(value.value, file.path);
130
+ if (c) {
131
+ nodePath.node.value = j.stringLiteral(result);
132
+ changed = true;
133
+ }
134
+ flagged.forEach((cls) => allFlagged.push(cls));
135
+ return;
136
+ }
137
+
138
+ if (value.type !== 'JSXExpressionContainer') return;
139
+ const { expression } = value;
140
+
141
+ // className={'pds-button--navbar'}
142
+ if (expression.type === 'StringLiteral') {
143
+ const {
144
+ result,
145
+ changed: c,
146
+ flagged,
147
+ } = processClasses(expression.value, file.path);
148
+ if (c) {
149
+ value.expression = j.stringLiteral(result);
150
+ changed = true;
151
+ }
152
+ flagged.forEach((cls) => allFlagged.push(cls));
153
+ return;
154
+ }
155
+
156
+ // className={`pds-button--navbar ${x}`}
157
+ if (expression.type === 'TemplateLiteral') {
158
+ expression.quasis.forEach((quasi) => {
159
+ const {
160
+ result,
161
+ changed: c,
162
+ flagged,
163
+ } = processClasses(quasi.value.raw, file.path);
164
+ if (c) {
165
+ quasi.value.raw = result;
166
+ quasi.value.cooked = result;
167
+ changed = true;
168
+ }
169
+ flagged.forEach((cls) => allFlagged.push(cls));
170
+ });
171
+ }
172
+ });
173
+
174
+ // Emit warnings for classes that need manual attention
175
+ if (allFlagged.length > 0) {
176
+ const unique = [...new Set(allFlagged)];
177
+ process.stderr.write(
178
+ `\n⚠ ${file.path}\n` +
179
+ unique
180
+ .map(
181
+ (cls) =>
182
+ ` ${cls} — removed in v2, no direct replacement. See migration guide.`,
183
+ )
184
+ .join('\n') +
185
+ '\n',
186
+ );
187
+ }
188
+
189
+ return changed ? root.toSource() : undefined;
190
+ };
191
+
192
+ module.exports.parser = 'tsx';
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Codemod: css-class-renames
3
+ *
4
+ * Renames PDS CSS utility classes in JSX className attributes.
5
+ * Driven entirely by cssClasses.renames in mappings.json.
6
+ *
7
+ * Usage:
8
+ * npx jscodeshift -t codemods/v1-to-v2/transforms/css-class-renames.js \
9
+ * --extensions=tsx,ts,jsx,js \
10
+ * src/
11
+ *
12
+ * Handles:
13
+ * - String literals: className="pds-overline-text foo"
14
+ * - Expression containers: className={'pds-overline-text'}
15
+ * - Template literals: className={`pds-overline-text ${styles.x}`}
16
+ *
17
+ * Does not handle:
18
+ * - CSS/SCSS files — run a find-and-replace over those separately.
19
+ * - Dynamic class names built at runtime (e.g. computed from variables).
20
+ *
21
+ * No --only flag: CSS utility classes can appear on any element (including
22
+ * native HTML), so there is no meaningful component-level scope to filter by.
23
+ * This transform is safe to run globally.
24
+ */
25
+
26
+ const path = require('path');
27
+ const mappings = require(path.join(__dirname, '../mappings.json'));
28
+
29
+ // Build lookup: { 'oldClass': 'newClass' }
30
+ function buildRenamesLookup() {
31
+ const lookup = {};
32
+ for (const entry of mappings.cssClasses.renames) {
33
+ if (entry.old && entry.new) lookup[entry.old] = entry.new;
34
+ }
35
+ return lookup;
36
+ }
37
+
38
+ const RENAMES = buildRenamesLookup();
39
+
40
+ // Replace class names in a space-separated class string.
41
+ // Splits on whitespace and does exact token matching, so 'pds-overline-text'
42
+ // won't incorrectly mangle 'pds-overline-text--sm'.
43
+ function renameClasses(classString) {
44
+ let changed = false;
45
+ const result = classString
46
+ .split(/(\s+)/)
47
+ .map((token) => {
48
+ // Preserve whitespace tokens unchanged
49
+ if (/^\s+$/.test(token) || token === '') return token;
50
+ const renamed = RENAMES[token];
51
+ if (renamed) {
52
+ changed = true;
53
+ return renamed;
54
+ }
55
+ return token;
56
+ })
57
+ .join('');
58
+ return { result, changed };
59
+ }
60
+
61
+ module.exports = function transform(file, api) {
62
+ const j = api.jscodeshift;
63
+ const root = j(file.source);
64
+
65
+ if (Object.keys(RENAMES).length === 0) return;
66
+
67
+ let changed = false;
68
+
69
+ root
70
+ .find(j.JSXAttribute, { name: { name: 'className' } })
71
+ .forEach((nodePath) => {
72
+ const { value } = nodePath.node;
73
+ if (!value) return;
74
+
75
+ // className="pds-overline-text foo"
76
+ if (value.type === 'StringLiteral') {
77
+ const { result, changed: c } = renameClasses(value.value);
78
+ if (c) {
79
+ nodePath.node.value = j.stringLiteral(result);
80
+ changed = true;
81
+ }
82
+ return;
83
+ }
84
+
85
+ if (value.type !== 'JSXExpressionContainer') return;
86
+ const { expression } = value;
87
+
88
+ // className={'pds-overline-text'}
89
+ if (expression.type === 'StringLiteral') {
90
+ const { result, changed: c } = renameClasses(expression.value);
91
+ if (c) {
92
+ value.expression = j.stringLiteral(result);
93
+ changed = true;
94
+ }
95
+ return;
96
+ }
97
+
98
+ // className={`pds-overline-text ${styles.x}`}
99
+ if (expression.type === 'TemplateLiteral') {
100
+ expression.quasis.forEach((quasi) => {
101
+ const { result, changed: c } = renameClasses(quasi.value.raw);
102
+ if (c) {
103
+ quasi.value.raw = result;
104
+ quasi.value.cooked = result;
105
+ changed = true;
106
+ }
107
+ });
108
+ }
109
+ });
110
+
111
+ return changed ? root.toSource() : undefined;
112
+ };
113
+
114
+ module.exports.parser = 'tsx';
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Codemod: icon-name-updates
3
+ *
4
+ * Rewrites removed icon names to their v2 equivalents on any PDS component
5
+ * that accepts an `iconName` prop.
6
+ *
7
+ * The icon registry is shared across all components, so any component using
8
+ * a renamed icon name (e.g. Button, ButtonLink, IconButton, Alert, etc.)
9
+ * needs updating — not just the Icon component itself.
10
+ *
11
+ * Driven by props.typeChanges[prop=iconName].migration in mappings.json.
12
+ *
13
+ * Usage (all components):
14
+ * npx jscodeshift -t codemods/v1-to-v2/transforms/icon-name-updates.js \
15
+ * --extensions=tsx,ts,jsx,js \
16
+ * src/
17
+ *
18
+ * Usage (single component — recommended for by-component migrations):
19
+ * npx jscodeshift -t codemods/v1-to-v2/transforms/icon-name-updates.js \
20
+ * --only=Button \
21
+ * --extensions=tsx,ts,jsx,js \
22
+ * src/
23
+ *
24
+ * Limitations:
25
+ * - Only static string literals and JSX expression containers are
26
+ * transformed. Dynamic values (variables, expressions) are left as-is
27
+ * and need manual review.
28
+ */
29
+
30
+ const path = require('path');
31
+ const mappings = require(path.join(__dirname, '../mappings.json'));
32
+
33
+ // Build a flat map of oldIconName → newIconName from all iconName typeChanges
34
+ function buildIconNameMap() {
35
+ const map = {};
36
+ for (const entry of mappings.props.typeChanges) {
37
+ if (entry.prop !== 'iconName' || !entry.migration) continue;
38
+ for (const [from, to] of Object.entries(entry.migration)) {
39
+ if (from === 'note') continue;
40
+ const cleanTo = to.split(' ')[0];
41
+ if (cleanTo !== from) map[from] = cleanTo;
42
+ }
43
+ }
44
+ return map;
45
+ }
46
+
47
+ const ICON_NAME_MAP = buildIconNameMap();
48
+
49
+ module.exports = function transform(file, api, options) {
50
+ const j = api.jscodeshift;
51
+ const root = j(file.source);
52
+
53
+ if (Object.keys(ICON_NAME_MAP).length === 0) return;
54
+
55
+ // Parse --only flag: comma-separated component names
56
+ const onlyComponents =
57
+ options && options.only
58
+ ? new Set(
59
+ options.only
60
+ .split(',')
61
+ .map((s) => s.trim())
62
+ .filter(Boolean),
63
+ )
64
+ : null;
65
+
66
+ // Map local import name → exported component name
67
+ const localToExported = new Map();
68
+
69
+ root.find(j.ImportDeclaration).forEach((nodePath) => {
70
+ if (nodePath.node.source.value !== '@pantheon-systems/pds-toolkit-react')
71
+ return;
72
+ nodePath.node.specifiers.forEach((spec) => {
73
+ if (spec.type === 'ImportSpecifier') {
74
+ localToExported.set(spec.local.name, spec.imported.name);
75
+ }
76
+ });
77
+ });
78
+
79
+ if (localToExported.size === 0) return;
80
+
81
+ let changed = false;
82
+
83
+ root.find(j.JSXOpeningElement).forEach((nodePath) => {
84
+ const nameNode = nodePath.node.name;
85
+ if (nameNode.type !== 'JSXIdentifier') return;
86
+
87
+ const exportedName = localToExported.get(nameNode.name);
88
+ if (!exportedName) return;
89
+
90
+ if (onlyComponents && !onlyComponents.has(exportedName)) return;
91
+
92
+ nodePath.node.attributes.forEach((attr) => {
93
+ if (attr.type !== 'JSXAttribute') return;
94
+ if (attr.name.name !== 'iconName') return;
95
+ if (!attr.value) return;
96
+
97
+ // iconName="circlePlusOutline"
98
+ if (attr.value.type === 'StringLiteral') {
99
+ const newName = ICON_NAME_MAP[attr.value.value];
100
+ if (newName) {
101
+ attr.value = j.stringLiteral(newName);
102
+ changed = true;
103
+ }
104
+ return;
105
+ }
106
+
107
+ // iconName={"circlePlusOutline"}
108
+ if (
109
+ attr.value.type === 'JSXExpressionContainer' &&
110
+ attr.value.expression.type === 'StringLiteral'
111
+ ) {
112
+ const newName = ICON_NAME_MAP[attr.value.expression.value];
113
+ if (newName) {
114
+ attr.value.expression = j.stringLiteral(newName);
115
+ changed = true;
116
+ }
117
+ }
118
+ });
119
+ });
120
+
121
+ return changed ? root.toSource() : undefined;
122
+ };
123
+
124
+ module.exports.parser = 'tsx';
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Codemod: prop-renames
3
+ *
4
+ * Renames PDS v1 prop names to their v2 names on JSX elements.
5
+ * Driven entirely by props.renames in mappings.json.
6
+ *
7
+ * Usage (all renames):
8
+ * npx jscodeshift -t codemods/v1-to-v2/transforms/prop-renames.js \
9
+ * --extensions=tsx,ts,jsx,js \
10
+ * src/
11
+ *
12
+ * Usage (single component — recommended for by-component migrations):
13
+ * npx jscodeshift -t codemods/v1-to-v2/transforms/prop-renames.js \
14
+ * --only=Button \
15
+ * --extensions=tsx,ts,jsx,js \
16
+ * src/
17
+ *
18
+ * Usage (multiple components):
19
+ * npx jscodeshift -t codemods/v1-to-v2/transforms/prop-renames.js \
20
+ * --only=Button,Icon \
21
+ * --extensions=tsx,ts,jsx,js \
22
+ * src/
23
+ *
24
+ * --only accepts a comma-separated list of component names.
25
+ *
26
+ * Limitations:
27
+ * - Renames where `from` is a nested path (e.g. `options[].description`)
28
+ * cannot be transformed at the JSX attribute level and are skipped.
29
+ * These require manual review.
30
+ */
31
+
32
+ const path = require('path');
33
+ const mappings = require(path.join(__dirname, '../mappings.json'));
34
+
35
+ // Build lookup: { 'ComponentName.oldPropName': 'newPropName' }
36
+ // Skips entries where `from` is a nested path (contains '.' or '[]').
37
+ function buildRenamesLookup() {
38
+ const lookup = {};
39
+ for (const entry of mappings.props.renames) {
40
+ const { component, from, to } = entry;
41
+ if (!component || !from || !to) continue;
42
+ // Skip nested paths — these can't be transformed at the attribute level
43
+ if (from.includes('.') || from.includes('[')) continue;
44
+ lookup[`${component}.${from}`] = to;
45
+ }
46
+ return lookup;
47
+ }
48
+
49
+ const ALL_RENAMES = buildRenamesLookup();
50
+
51
+ module.exports = function transform(file, api, options) {
52
+ const j = api.jscodeshift;
53
+ const root = j(file.source);
54
+
55
+ // Parse --only flag: comma-separated component names
56
+ const onlyComponents =
57
+ options && options.only
58
+ ? new Set(
59
+ options.only
60
+ .split(',')
61
+ .map((s) => s.trim())
62
+ .filter(Boolean),
63
+ )
64
+ : null;
65
+
66
+ // Map local import name → exported component name
67
+ const localToExported = new Map();
68
+
69
+ root.find(j.ImportDeclaration).forEach((nodePath) => {
70
+ if (nodePath.node.source.value !== '@pantheon-systems/pds-toolkit-react')
71
+ return;
72
+ nodePath.node.specifiers.forEach((spec) => {
73
+ if (spec.type === 'ImportSpecifier') {
74
+ localToExported.set(spec.local.name, spec.imported.name);
75
+ }
76
+ });
77
+ });
78
+
79
+ if (localToExported.size === 0) return;
80
+
81
+ let changed = false;
82
+
83
+ root.find(j.JSXOpeningElement).forEach((nodePath) => {
84
+ const nameNode = nodePath.node.name;
85
+ if (nameNode.type !== 'JSXIdentifier') return;
86
+
87
+ const exportedName = localToExported.get(nameNode.name);
88
+ if (!exportedName) return;
89
+
90
+ if (onlyComponents && !onlyComponents.has(exportedName)) return;
91
+
92
+ nodePath.node.attributes.forEach((attr) => {
93
+ if (attr.type !== 'JSXAttribute') return;
94
+
95
+ const propName = attr.name.name;
96
+ if (typeof propName !== 'string') return;
97
+
98
+ const newPropName = ALL_RENAMES[`${exportedName}.${propName}`];
99
+ if (!newPropName) return;
100
+
101
+ attr.name = j.jsxIdentifier(newPropName);
102
+ changed = true;
103
+ });
104
+ });
105
+
106
+ return changed ? root.toSource() : undefined;
107
+ };
108
+
109
+ module.exports.parser = 'tsx';
@@ -4,10 +4,26 @@
4
4
  * Standardizes size-related prop values across PDS v1→v2.
5
5
  * Driven entirely by props.typeChanges[].migration in mappings.json.
6
6
  *
7
- * Usage:
7
+ * Usage (all components):
8
8
  * npx jscodeshift -t codemods/v1-to-v2/transforms/prop-value-standardization.js \
9
9
  * --extensions=tsx,ts,jsx,js \
10
10
  * src/
11
+ *
12
+ * Usage (single component — recommended for by-component migrations):
13
+ * npx jscodeshift -t codemods/v1-to-v2/transforms/prop-value-standardization.js \
14
+ * --only=Spinner \
15
+ * --extensions=tsx,ts,jsx,js \
16
+ * src/
17
+ *
18
+ * Usage (multiple components):
19
+ * npx jscodeshift -t codemods/v1-to-v2/transforms/prop-value-standardization.js \
20
+ * --only=Spinner,Avatar \
21
+ * --extensions=tsx,ts,jsx,js \
22
+ * src/
23
+ *
24
+ * --only accepts a comma-separated list of exported component names. When
25
+ * provided, only rules for those components are applied. This prevents the
26
+ * transform from rewriting size values on components that are still on V1.
11
27
  */
12
28
 
13
29
  const path = require('path');
@@ -43,10 +59,22 @@ function buildMigrationLookup() {
43
59
 
44
60
  const MIGRATION_LOOKUP = buildMigrationLookup();
45
61
 
46
- module.exports = function transform(file, api) {
62
+ module.exports = function transform(file, api, options) {
47
63
  const j = api.jscodeshift;
48
64
  const root = j(file.source);
49
65
 
66
+ // Parse --only flag into a Set of component names for O(1) lookup.
67
+ // Accepts comma-separated values: --only=Spinner,Avatar
68
+ const onlyComponents =
69
+ options && options.only
70
+ ? new Set(
71
+ options.only
72
+ .split(',')
73
+ .map((s) => s.trim())
74
+ .filter(Boolean),
75
+ )
76
+ : null;
77
+
50
78
  // Map local import name → exported component name
51
79
  // Handles aliased imports: import { Spinner as MySpinner } → { MySpinner: 'Spinner' }
52
80
  const localToExported = new Map();
@@ -73,6 +101,9 @@ module.exports = function transform(file, api) {
73
101
  const exportedName = localToExported.get(nameNode.name);
74
102
  if (!exportedName) return;
75
103
 
104
+ // Skip this component if --only is set and it isn't in the list
105
+ if (onlyComponents && !onlyComponents.has(exportedName)) return;
106
+
76
107
  nodePath.node.attributes.forEach((attr) => {
77
108
  if (attr.type !== 'JSXAttribute' || !attr.value) return;
78
109