@machinemetrics/mm-react-components 0.2.0 → 0.2.3-1
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/README.md +73 -25
- package/dist/App.d.ts.map +1 -1
- package/dist/README.md +266 -0
- package/dist/components/ui/accordion.d.ts +10 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/badge.d.ts +10 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/button.d.ts +4 -16
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/calendar.d.ts +9 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/checkbox.d.ts +0 -12
- package/dist/components/ui/checkbox.d.ts.map +1 -1
- package/dist/components/ui/collapsible.d.ts +7 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/data-table/TableView.d.ts +3 -0
- package/dist/components/ui/data-table/TableView.d.ts.map +1 -0
- package/dist/components/ui/data-table/cards/ResponsiveTable.d.ts +10 -0
- package/dist/components/ui/data-table/cards/ResponsiveTable.d.ts.map +1 -0
- package/dist/components/ui/data-table/cards/RowCard.d.ts +9 -0
- package/dist/components/ui/data-table/cards/RowCard.d.ts.map +1 -0
- package/dist/components/ui/data-table/cards/index.d.ts +2 -0
- package/dist/components/ui/data-table/cards/index.d.ts.map +1 -0
- package/dist/components/ui/data-table/columnTypes/badgeColumn.d.ts +16 -0
- package/dist/components/ui/data-table/columnTypes/badgeColumn.d.ts.map +1 -0
- package/dist/components/ui/data-table/columnTypes/createColumn.d.ts +8 -0
- package/dist/components/ui/data-table/columnTypes/createColumn.d.ts.map +1 -0
- package/dist/components/ui/data-table/columnTypes/dateColumn.d.ts +13 -0
- package/dist/components/ui/data-table/columnTypes/dateColumn.d.ts.map +1 -0
- package/dist/components/ui/data-table/columnTypes/index.d.ts +8 -0
- package/dist/components/ui/data-table/columnTypes/index.d.ts.map +1 -0
- package/dist/components/ui/data-table/columnTypes/multiBadgeColumn.d.ts +16 -0
- package/dist/components/ui/data-table/columnTypes/multiBadgeColumn.d.ts.map +1 -0
- package/dist/components/ui/data-table/columnTypes/numericColumn.d.ts +15 -0
- package/dist/components/ui/data-table/columnTypes/numericColumn.d.ts.map +1 -0
- package/dist/components/ui/data-table/columnTypes/selectionColumn.d.ts +10 -0
- package/dist/components/ui/data-table/columnTypes/selectionColumn.d.ts.map +1 -0
- package/dist/components/ui/data-table/columnTypes/textColumn.d.ts +13 -0
- package/dist/components/ui/data-table/columnTypes/textColumn.d.ts.map +1 -0
- package/dist/components/ui/data-table/index.d.ts +14 -0
- package/dist/components/ui/data-table/index.d.ts.map +1 -0
- package/dist/components/ui/data-table/metadata/ColumnRegistry.d.ts +90 -0
- package/dist/components/ui/data-table/metadata/ColumnRegistry.d.ts.map +1 -0
- package/dist/components/ui/data-table/metadata/alignment.d.ts +8 -0
- package/dist/components/ui/data-table/metadata/alignment.d.ts.map +1 -0
- package/dist/components/ui/data-table/pagination.d.ts +9 -0
- package/dist/components/ui/data-table/pagination.d.ts.map +1 -0
- package/dist/components/ui/data-table/state/index.d.ts +3 -0
- package/dist/components/ui/data-table/state/index.d.ts.map +1 -0
- package/dist/components/ui/data-table/state/useBreakpoint.d.ts +2 -0
- package/dist/components/ui/data-table/state/useBreakpoint.d.ts.map +1 -0
- package/dist/components/ui/data-table/state/useDataTableState.d.ts +19 -0
- package/dist/components/ui/data-table/state/useDataTableState.d.ts.map +1 -0
- package/dist/components/ui/data-table/tokens.d.ts +10 -0
- package/dist/components/ui/data-table/tokens.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/ColumnVisibilityMenu.d.ts +8 -0
- package/dist/components/ui/data-table/toolbar/ColumnVisibilityMenu.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/DataTableToolbar.d.ts +15 -0
- package/dist/components/ui/data-table/toolbar/DataTableToolbar.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/MenuHeader.d.ts +8 -0
- package/dist/components/ui/data-table/toolbar/MenuHeader.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/SortMenu.d.ts +7 -0
- package/dist/components/ui/data-table/toolbar/SortMenu.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/filters/DateRangeFilter.d.ts +5 -0
- package/dist/components/ui/data-table/toolbar/filters/DateRangeFilter.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/filters/FilterMenu.d.ts +10 -0
- package/dist/components/ui/data-table/toolbar/filters/FilterMenu.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/filters/InlineDateRangePicker.d.ts +10 -0
- package/dist/components/ui/data-table/toolbar/filters/InlineDateRangePicker.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/filters/NumericRangeFilter.d.ts +5 -0
- package/dist/components/ui/data-table/toolbar/filters/NumericRangeFilter.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/filters/SelectFilter.d.ts +6 -0
- package/dist/components/ui/data-table/toolbar/filters/SelectFilter.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/filters/TextFilter.d.ts +5 -0
- package/dist/components/ui/data-table/toolbar/filters/TextFilter.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/filters/index.d.ts +9 -0
- package/dist/components/ui/data-table/toolbar/filters/index.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/filters/types.d.ts +26 -0
- package/dist/components/ui/data-table/toolbar/filters/types.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/filters/useColumnFilters.d.ts +5 -0
- package/dist/components/ui/data-table/toolbar/filters/useColumnFilters.d.ts.map +1 -0
- package/dist/components/ui/data-table/toolbar/index.d.ts +5 -0
- package/dist/components/ui/data-table/toolbar/index.d.ts.map +1 -0
- package/dist/components/ui/data-table/types.d.ts +40 -0
- package/dist/components/ui/data-table/types.d.ts.map +1 -0
- package/dist/components/ui/data-table/useTableController.d.ts +29 -0
- package/dist/components/ui/data-table/useTableController.d.ts.map +1 -0
- package/dist/components/ui/data-table/utils.d.ts +3 -0
- package/dist/components/ui/data-table/utils.d.ts.map +1 -0
- package/dist/components/ui/date-range-picker.d.ts +9 -0
- package/dist/components/ui/date-range-picker.d.ts.map +1 -0
- package/dist/components/ui/dialog.d.ts +16 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/drawer.d.ts +16 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +28 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropzone/index.d.ts +25 -0
- package/dist/components/ui/dropzone/index.d.ts.map +1 -0
- package/dist/components/ui/input.d.ts +2 -15
- package/dist/components/ui/input.d.ts.map +1 -1
- package/dist/components/ui/label.d.ts +0 -9
- package/dist/components/ui/label.d.ts.map +1 -1
- package/dist/components/ui/page-header/index.d.ts +3 -0
- package/dist/components/ui/page-header/index.d.ts.map +1 -0
- package/dist/components/ui/page-header/page-header-types.d.ts +44 -0
- package/dist/components/ui/page-header/page-header-types.d.ts.map +1 -0
- package/dist/components/ui/page-header/page-header.d.ts +5 -0
- package/dist/components/ui/page-header/page-header.d.ts.map +1 -0
- package/dist/components/ui/popover.d.ts +8 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/progress.d.ts +5 -0
- package/dist/components/ui/progress.d.ts.map +1 -0
- package/dist/components/ui/radio-group.d.ts +0 -19
- package/dist/components/ui/radio-group.d.ts.map +1 -1
- package/dist/components/ui/search-input.d.ts +7 -0
- package/dist/components/ui/search-input.d.ts.map +1 -0
- package/dist/components/ui/select.d.ts +16 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/sheet.d.ts +14 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/skeleton.d.ts +4 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/slider.d.ts +15 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/switch.d.ts +5 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/table/Table.d.ts +21 -0
- package/dist/components/ui/table/Table.d.ts.map +1 -0
- package/dist/components/ui/table/index.d.ts +2 -0
- package/dist/components/ui/table/index.d.ts.map +1 -0
- package/dist/components/ui/table.d.ts +11 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/tabs.d.ts +15 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/toggle.d.ts +10 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/tooltip.d.ts +8 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/docs/TAILWIND_SETUP.md +339 -0
- package/dist/index.css +536 -0
- package/dist/index.d.ts +26 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/page-header-utils.d.ts +2 -0
- package/dist/lib/page-header-utils.d.ts.map +1 -0
- package/dist/mm-react-components.es.js +30238 -1350
- package/dist/mm-react-components.es.js.map +1 -1
- package/dist/mm-react-components.umd.js +41 -6
- package/dist/mm-react-components.umd.js.map +1 -1
- package/dist/preview/AccordionPreview.d.ts +2 -0
- package/dist/preview/AccordionPreview.d.ts.map +1 -0
- package/dist/preview/BadgePreview.d.ts +2 -0
- package/dist/preview/BadgePreview.d.ts.map +1 -0
- package/dist/preview/CalendarPreview.d.ts +2 -0
- package/dist/preview/CalendarPreview.d.ts.map +1 -0
- package/dist/preview/CollapsiblePreview.d.ts +2 -0
- package/dist/preview/CollapsiblePreview.d.ts.map +1 -0
- package/dist/preview/DataTablePreview.d.ts +9 -0
- package/dist/preview/DataTablePreview.d.ts.map +1 -0
- package/dist/preview/DateRangePickerPreview.d.ts +2 -0
- package/dist/preview/DateRangePickerPreview.d.ts.map +1 -0
- package/dist/preview/DialogPreview.d.ts +2 -0
- package/dist/preview/DialogPreview.d.ts.map +1 -0
- package/dist/preview/DrawerPreview.d.ts +2 -0
- package/dist/preview/DrawerPreview.d.ts.map +1 -0
- package/dist/preview/DropdownMenuPreview.d.ts +2 -0
- package/dist/preview/DropdownMenuPreview.d.ts.map +1 -0
- package/dist/preview/DropzonePreview.d.ts +2 -0
- package/dist/preview/DropzonePreview.d.ts.map +1 -0
- package/dist/preview/InputPreview.d.ts.map +1 -1
- package/dist/preview/PopoverPreview.d.ts +2 -0
- package/dist/preview/PopoverPreview.d.ts.map +1 -0
- package/dist/preview/ProgressPreview.d.ts +2 -0
- package/dist/preview/ProgressPreview.d.ts.map +1 -0
- package/dist/preview/SelectPreview.d.ts +2 -0
- package/dist/preview/SelectPreview.d.ts.map +1 -0
- package/dist/preview/SheetPreview.d.ts +2 -0
- package/dist/preview/SheetPreview.d.ts.map +1 -0
- package/dist/preview/SkeletonPreview.d.ts +2 -0
- package/dist/preview/SkeletonPreview.d.ts.map +1 -0
- package/dist/preview/SliderPreview.d.ts +2 -0
- package/dist/preview/SliderPreview.d.ts.map +1 -0
- package/dist/preview/SwitchPreview.d.ts +2 -0
- package/dist/preview/SwitchPreview.d.ts.map +1 -0
- package/dist/preview/TablePreview.d.ts +2 -0
- package/dist/preview/TablePreview.d.ts.map +1 -0
- package/dist/preview/TabsPreview.d.ts +2 -0
- package/dist/preview/TabsPreview.d.ts.map +1 -0
- package/dist/preview/TogglePreview.d.ts +2 -0
- package/dist/preview/TogglePreview.d.ts.map +1 -0
- package/dist/preview/TooltipPreview.d.ts +2 -0
- package/dist/preview/TooltipPreview.d.ts.map +1 -0
- package/dist/preview/data-table/data-table-preview_column-content.d.ts +18 -0
- package/dist/preview/data-table/data-table-preview_column-content.d.ts.map +1 -0
- package/dist/preview/page-header/PageHeaderPreview.d.ts +3 -0
- package/dist/preview/page-header/PageHeaderPreview.d.ts.map +1 -0
- package/dist/tailwind.config.export.js +153 -0
- package/dist/themes/carbide.css +1257 -0
- package/dist/themes/complete.css +8 -0
- package/docs/TAILWIND_SETUP.md +339 -0
- package/package.json +38 -5
- package/scripts/README.md +171 -0
- package/scripts/chakra-to-shadcn-migrator/README.md +107 -0
- package/scripts/chakra-to-shadcn-migrator/bin/chakra-to-shadcn.js +1135 -0
- package/src/index.css +536 -0
- package/src/themes/carbide.css +1257 -0
- package/src/themes/complete.css +8 -0
- package/tailwind.config.export.js +153 -0
|
@@ -0,0 +1,1135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import process from 'process';
|
|
5
|
+
import { bold, colorize } from '../lib/colors.js';
|
|
6
|
+
import { parseArgs } from '../lib/args.js';
|
|
7
|
+
import { resolveConfig, loadJson } from '../lib/config.js';
|
|
8
|
+
import { updateDependencies, renderDependencyReport } from '../lib/deps.js';
|
|
9
|
+
import { collectSourceFiles } from '../lib/file-io.js';
|
|
10
|
+
import {
|
|
11
|
+
renderImportReport,
|
|
12
|
+
renderProviderInstructions,
|
|
13
|
+
renderPostSteps,
|
|
14
|
+
} from '../lib/render.js';
|
|
15
|
+
|
|
16
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
17
|
+
const projectRoot = path.resolve(process.cwd(), opts.project);
|
|
18
|
+
const configPath = resolveConfig(projectRoot, opts.config);
|
|
19
|
+
const config = configPath ? loadJson(configPath) : undefined;
|
|
20
|
+
|
|
21
|
+
if (!config) {
|
|
22
|
+
console.error(
|
|
23
|
+
colorize(
|
|
24
|
+
'Unable to locate chakra-to-shadcn.config.json. Provide one with --config.',
|
|
25
|
+
'red',
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const moduleNames = Object.keys(config.moduleMappings ?? {});
|
|
32
|
+
|
|
33
|
+
console.log(bold(colorize('\nChakra UI → Shadcn migration helper', 'cyan')));
|
|
34
|
+
console.log(`Project: ${colorize(projectRoot, 'green')}`);
|
|
35
|
+
console.log(`Configuration: ${colorize(configPath, 'green')}`);
|
|
36
|
+
console.log(
|
|
37
|
+
`Mode: ${opts.apply ? colorize('apply changes', 'green') : colorize('dry-run / plan', 'yellow')}`,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const dependencyReport = updateDependencies(projectRoot, config, opts.apply);
|
|
41
|
+
renderDependencyReport(dependencyReport, opts.apply, { bold, colorize });
|
|
42
|
+
renderProviderInstructions(config, { bold });
|
|
43
|
+
|
|
44
|
+
if (moduleNames.length > 0) {
|
|
45
|
+
const sourceFiles = collectSourceFiles(
|
|
46
|
+
projectRoot,
|
|
47
|
+
opts.source,
|
|
48
|
+
opts.includeTests,
|
|
49
|
+
);
|
|
50
|
+
const importReports = analyzeImports(
|
|
51
|
+
sourceFiles,
|
|
52
|
+
moduleNames,
|
|
53
|
+
config,
|
|
54
|
+
opts.apply,
|
|
55
|
+
);
|
|
56
|
+
renderImportReport(importReports, opts.apply, config, { colorize });
|
|
57
|
+
} else {
|
|
58
|
+
console.log('\nNo module mappings configured.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
renderPostSteps(config, { bold });
|
|
62
|
+
|
|
63
|
+
function analyzeImports(files, moduleNames, config, applyMode) {
|
|
64
|
+
console.log(bold('\nStep 3 – Replace Chakra imports'));
|
|
65
|
+
const reports = [];
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
const text = fs.readFileSync(file.absolute, 'utf8');
|
|
68
|
+
let mutated = text;
|
|
69
|
+
let dirty = false;
|
|
70
|
+
const fileReport = { file: file.relative, modules: [], applied: false };
|
|
71
|
+
const usedSpecifiersByModule = new Map();
|
|
72
|
+
for (const moduleName of moduleNames) {
|
|
73
|
+
const moduleConfig = config.moduleMappings[moduleName];
|
|
74
|
+
if (!moduleConfig) continue;
|
|
75
|
+
const matches = findImports(mutated, moduleName);
|
|
76
|
+
for (const match of matches.reverse()) {
|
|
77
|
+
const result = transformImport(match, moduleConfig, applyMode);
|
|
78
|
+
fileReport.modules.push({ module: moduleName, ...result });
|
|
79
|
+
// Track specifiers used so we can target JSX replacement to only those imported
|
|
80
|
+
const used = usedSpecifiersByModule.get(moduleName) ?? new Set();
|
|
81
|
+
for (const s of match.specifiers) {
|
|
82
|
+
if (s?.local) used.add(s.local);
|
|
83
|
+
else if (s?.imported) used.add(s.imported);
|
|
84
|
+
}
|
|
85
|
+
usedSpecifiersByModule.set(moduleName, used);
|
|
86
|
+
if (applyMode && result && result.replacement) {
|
|
87
|
+
mutated =
|
|
88
|
+
mutated.slice(0, match.start) +
|
|
89
|
+
result.replacement +
|
|
90
|
+
mutated.slice(match.end);
|
|
91
|
+
dirty = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// JSX tag replacement based on config.jsxSpecifiers
|
|
95
|
+
const jsxSpec = moduleConfig.jsxSpecifiers;
|
|
96
|
+
if (jsxSpec) {
|
|
97
|
+
const used = usedSpecifiersByModule.get(moduleName) ?? new Set();
|
|
98
|
+
const jsxResult = transformJSX(mutated, jsxSpec, used, applyMode);
|
|
99
|
+
if (jsxResult && jsxResult.changed) {
|
|
100
|
+
if (applyMode && jsxResult.output !== mutated) {
|
|
101
|
+
mutated = jsxResult.output;
|
|
102
|
+
dirty = true;
|
|
103
|
+
}
|
|
104
|
+
fileReport.modules.push({
|
|
105
|
+
module: moduleName,
|
|
106
|
+
jsxReplaced: Array.from(jsxResult.replaced ?? []),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Transform compound components (e.g., Dialog.Root, Tabs.List)
|
|
112
|
+
const compoundSpec = moduleConfig.compoundComponents;
|
|
113
|
+
if (compoundSpec && applyMode) {
|
|
114
|
+
const compoundResult = transformCompoundComponents(
|
|
115
|
+
mutated,
|
|
116
|
+
compoundSpec,
|
|
117
|
+
);
|
|
118
|
+
if (compoundResult.changed) {
|
|
119
|
+
mutated = compoundResult.output;
|
|
120
|
+
dirty = true;
|
|
121
|
+
if (compoundResult.transformed.length > 0) {
|
|
122
|
+
fileReport.modules.push({
|
|
123
|
+
module: moduleName,
|
|
124
|
+
compoundTransformed: compoundResult.transformed,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Add imports for new compound components
|
|
128
|
+
const newImports = compoundResult.newComponents;
|
|
129
|
+
if (newImports.length > 0) {
|
|
130
|
+
// Find existing imports from mm-react-components
|
|
131
|
+
const mmImportPattern =
|
|
132
|
+
/import\s+\{([^}]*)\}\s+from\s+['"]@machinemetrics\/mm-react-components['"];?/;
|
|
133
|
+
const match = mutated.match(mmImportPattern);
|
|
134
|
+
|
|
135
|
+
if (match) {
|
|
136
|
+
// Add to existing import and deduplicate
|
|
137
|
+
const existingImports = match[1].trim();
|
|
138
|
+
const existingList = existingImports
|
|
139
|
+
.split(',')
|
|
140
|
+
.map((s) => s.trim())
|
|
141
|
+
.filter(Boolean);
|
|
142
|
+
const allImports = [
|
|
143
|
+
...new Set([...existingList, ...newImports]),
|
|
144
|
+
];
|
|
145
|
+
mutated = mutated.replace(
|
|
146
|
+
mmImportPattern,
|
|
147
|
+
`import { ${allImports.join(', ')} } from '@machinemetrics/mm-react-components';`,
|
|
148
|
+
);
|
|
149
|
+
} else {
|
|
150
|
+
// Add new import at top of file (after existing imports)
|
|
151
|
+
const firstImportEnd = mutated.indexOf(';') + 1;
|
|
152
|
+
const newImport = `\nimport { ${newImports.join(', ')} } from '@machinemetrics/mm-react-components';`;
|
|
153
|
+
mutated =
|
|
154
|
+
mutated.slice(0, firstImportEnd) +
|
|
155
|
+
newImport +
|
|
156
|
+
mutated.slice(firstImportEnd);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Cleanup unused Chakra imports
|
|
165
|
+
if (applyMode && dirty) {
|
|
166
|
+
const cleanupResult = cleanupUnusedImports(mutated, moduleNames);
|
|
167
|
+
if (cleanupResult.changed) {
|
|
168
|
+
mutated = cleanupResult.output;
|
|
169
|
+
fileReport.importsRemoved = cleanupResult.removed;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (applyMode && dirty && mutated !== text) {
|
|
174
|
+
fs.writeFileSync(file.absolute, mutated, 'utf8');
|
|
175
|
+
fileReport.applied = true;
|
|
176
|
+
}
|
|
177
|
+
if (fileReport.modules.length) {
|
|
178
|
+
reports.push(fileReport);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return reports;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Clean up unused Chakra imports after transformation
|
|
185
|
+
function cleanupUnusedImports(content, moduleNames) {
|
|
186
|
+
let output = content;
|
|
187
|
+
let changed = false;
|
|
188
|
+
const removed = [];
|
|
189
|
+
|
|
190
|
+
for (const moduleName of moduleNames) {
|
|
191
|
+
const imports = findImports(output, moduleName);
|
|
192
|
+
|
|
193
|
+
for (const importMatch of imports.reverse()) {
|
|
194
|
+
const unusedSpecifiers = [];
|
|
195
|
+
const usedSpecifiers = [];
|
|
196
|
+
|
|
197
|
+
for (const spec of importMatch.specifiers) {
|
|
198
|
+
const name = spec.local || spec.imported;
|
|
199
|
+
// Check if this import is actually used in the code (outside of imports)
|
|
200
|
+
// Look for the name as a JSX tag or in code
|
|
201
|
+
const codeWithoutImports = output.replace(
|
|
202
|
+
/import\s+.*?from\s+['"].*?['"];?/gs,
|
|
203
|
+
'',
|
|
204
|
+
);
|
|
205
|
+
const usagePattern = new RegExp(
|
|
206
|
+
`<${escapeRegex(name)}[\\s/>]|\\b${escapeRegex(name)}\\b`,
|
|
207
|
+
'g',
|
|
208
|
+
);
|
|
209
|
+
const isUsed = usagePattern.test(codeWithoutImports);
|
|
210
|
+
|
|
211
|
+
if (isUsed) {
|
|
212
|
+
usedSpecifiers.push(spec);
|
|
213
|
+
} else {
|
|
214
|
+
unusedSpecifiers.push(spec);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (unusedSpecifiers.length > 0) {
|
|
219
|
+
if (usedSpecifiers.length === 0) {
|
|
220
|
+
// Remove entire import statement
|
|
221
|
+
output =
|
|
222
|
+
output.slice(0, importMatch.start) + output.slice(importMatch.end);
|
|
223
|
+
changed = true;
|
|
224
|
+
removed.push({
|
|
225
|
+
module: moduleName,
|
|
226
|
+
specifiers: unusedSpecifiers.map((s) => s.local || s.imported),
|
|
227
|
+
});
|
|
228
|
+
} else {
|
|
229
|
+
// Keep only used specifiers
|
|
230
|
+
const isTypeOnly = importMatch.raw.includes('import type');
|
|
231
|
+
const specifierList = usedSpecifiers
|
|
232
|
+
.map((s) => {
|
|
233
|
+
const typePrefix = s.isType && !isTypeOnly ? 'type ' : '';
|
|
234
|
+
if (s.local && s.imported && s.local !== s.imported) {
|
|
235
|
+
return `${typePrefix}${s.imported} as ${s.local}`;
|
|
236
|
+
}
|
|
237
|
+
return `${typePrefix}${s.local || s.imported}`;
|
|
238
|
+
})
|
|
239
|
+
.join(', ');
|
|
240
|
+
|
|
241
|
+
const keyword = isTypeOnly ? 'import type' : 'import';
|
|
242
|
+
const newImport = `${keyword} { ${specifierList} } from '${moduleName}';`;
|
|
243
|
+
output =
|
|
244
|
+
output.slice(0, importMatch.start) +
|
|
245
|
+
newImport +
|
|
246
|
+
output.slice(importMatch.end);
|
|
247
|
+
changed = true;
|
|
248
|
+
removed.push({
|
|
249
|
+
module: moduleName,
|
|
250
|
+
specifiers: unusedSpecifiers.map((s) => s.local || s.imported),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { output, changed, removed };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function escapeRegex(text) {
|
|
261
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function findImports(content, moduleName) {
|
|
265
|
+
const pattern = new RegExp(
|
|
266
|
+
`import\\s+(type\\s+)?\\{([^}]*)\\}\\s+from\\s+['"]${escapeRegex(moduleName)}['"];?`,
|
|
267
|
+
'gm',
|
|
268
|
+
);
|
|
269
|
+
const imports = [];
|
|
270
|
+
let match;
|
|
271
|
+
while ((match = pattern.exec(content))) {
|
|
272
|
+
const [full, typeKeyword, specifierBlock] = match;
|
|
273
|
+
const start = match.index;
|
|
274
|
+
const end = start + full.length;
|
|
275
|
+
const isTypeOnly = Boolean(typeKeyword);
|
|
276
|
+
const specifiers = parseSpecifiers(specifierBlock, isTypeOnly);
|
|
277
|
+
imports.push({ start, end, specifiers, raw: full });
|
|
278
|
+
}
|
|
279
|
+
return imports;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function parseSpecifiers(block, isTypeOnly) {
|
|
283
|
+
return block
|
|
284
|
+
.split(',')
|
|
285
|
+
.map((part) => part.trim())
|
|
286
|
+
.filter(Boolean)
|
|
287
|
+
.map((part) => {
|
|
288
|
+
let spec = part;
|
|
289
|
+
let specIsType = isTypeOnly;
|
|
290
|
+
if (spec.startsWith('type ')) {
|
|
291
|
+
specIsType = true;
|
|
292
|
+
spec = spec.slice(5).trim();
|
|
293
|
+
}
|
|
294
|
+
const [importedRaw, localRaw] = spec.split(/\s+as\s+/i);
|
|
295
|
+
const imported = (importedRaw ?? '').trim();
|
|
296
|
+
const local = (localRaw ?? importedRaw ?? '').trim();
|
|
297
|
+
return { imported, local, isType: specIsType };
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function transformImport(importInfo, moduleConfig, applyMode) {
|
|
302
|
+
if (!moduleConfig) {
|
|
303
|
+
return { missingConfig: true, specifiers: importInfo.specifiers };
|
|
304
|
+
}
|
|
305
|
+
const convertible = [];
|
|
306
|
+
const manual = [];
|
|
307
|
+
const jsxHandled = new Set(Object.keys(moduleConfig.jsxSpecifiers ?? {}));
|
|
308
|
+
|
|
309
|
+
for (const specifier of importInfo.specifiers) {
|
|
310
|
+
const mapping =
|
|
311
|
+
moduleConfig.specifiers?.[specifier.imported] ||
|
|
312
|
+
moduleConfig.specifiers?.[specifier.local] ||
|
|
313
|
+
moduleConfig.default;
|
|
314
|
+
if (!mapping) {
|
|
315
|
+
// If we have a JSX replacement for this specifier, treat it as handled (to be dropped from imports)
|
|
316
|
+
if (
|
|
317
|
+
jsxHandled.has(specifier.imported) ||
|
|
318
|
+
jsxHandled.has(specifier.local)
|
|
319
|
+
) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
manual.push(specifier);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
convertible.push({ specifier, mapping });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (!applyMode) {
|
|
329
|
+
return { convertible, manual };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Build replacement imports even if some manual specifiers remain; we'll keep the remaining Chakra specifiers
|
|
333
|
+
|
|
334
|
+
const grouped = new Map();
|
|
335
|
+
for (const entry of convertible) {
|
|
336
|
+
const { specifier, mapping } = entry;
|
|
337
|
+
const modulePath = mapping.module ?? moduleConfig.default?.module;
|
|
338
|
+
if (!modulePath) {
|
|
339
|
+
manual.push(specifier);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const importName = mapping.import ?? specifier.imported;
|
|
343
|
+
const localName = mapping.alias ?? specifier.local;
|
|
344
|
+
const isType = mapping.type === true || specifier.isType;
|
|
345
|
+
const key = `${modulePath}|${isType ? 'type' : 'value'}`;
|
|
346
|
+
if (!grouped.has(key)) {
|
|
347
|
+
grouped.set(key, { modulePath, isType, names: [] });
|
|
348
|
+
}
|
|
349
|
+
grouped.get(key).names.push({ importName, localName });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Construct import lines for mapped specifiers
|
|
353
|
+
|
|
354
|
+
const parts = [];
|
|
355
|
+
for (const group of grouped.values()) {
|
|
356
|
+
const names = group.names
|
|
357
|
+
.map(({ importName, localName }) =>
|
|
358
|
+
importName === localName ? importName : `${importName} as ${localName}`,
|
|
359
|
+
)
|
|
360
|
+
.join(', ');
|
|
361
|
+
const keyword = group.isType ? 'import type' : 'import';
|
|
362
|
+
parts.push(`${keyword} { ${names} } from '${group.modulePath}';`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Keep remaining manual specifiers (that are not JSX-handled) from the original module
|
|
366
|
+
if (manual.length) {
|
|
367
|
+
const remaining = manual.map((s) => s.local || s.imported).filter(Boolean);
|
|
368
|
+
if (remaining.length) {
|
|
369
|
+
parts.unshift(
|
|
370
|
+
`import { ${remaining.join(', ')} } from '${moduleConfig.moduleName || ''}@chakra-ui/react';`,
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return { convertible, manual, replacement: parts.join(' ') };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// renderImportReport and renderPostSteps now come from lib/render.js
|
|
379
|
+
|
|
380
|
+
// --- JSX Transformation Helpers ---
|
|
381
|
+
|
|
382
|
+
// Transform compound components like Dialog.Root, Tabs.List, etc.
|
|
383
|
+
function transformCompoundComponents(input, compoundSpec) {
|
|
384
|
+
if (!input)
|
|
385
|
+
return {
|
|
386
|
+
output: input,
|
|
387
|
+
changed: false,
|
|
388
|
+
transformed: [],
|
|
389
|
+
newComponents: [],
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
let output = input;
|
|
393
|
+
let changed = false;
|
|
394
|
+
const transformed = [];
|
|
395
|
+
const newComponents = new Set();
|
|
396
|
+
|
|
397
|
+
// Process each compound component mapping
|
|
398
|
+
for (const [compoundName, config] of Object.entries(compoundSpec)) {
|
|
399
|
+
const escapedName = escapeRegex(compoundName);
|
|
400
|
+
|
|
401
|
+
if (config.remove) {
|
|
402
|
+
// Remove the component entirely (keep children)
|
|
403
|
+
// Opening tags: <Dialog.Backdrop ...>...</Dialog.Backdrop> → ...
|
|
404
|
+
const pattern = new RegExp(
|
|
405
|
+
`<${escapedName}([^>]*?)>([\\s\\S]*?)<\\/${escapedName}>`,
|
|
406
|
+
'g',
|
|
407
|
+
);
|
|
408
|
+
const newOutput = output.replace(pattern, (match, attrs, children) => {
|
|
409
|
+
transformed.push(compoundName);
|
|
410
|
+
return children;
|
|
411
|
+
});
|
|
412
|
+
if (newOutput !== output) {
|
|
413
|
+
output = newOutput;
|
|
414
|
+
changed = true;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Self-closing tags: <Dialog.Backdrop ... /> → (remove entirely)
|
|
418
|
+
const selfPattern = new RegExp(`<${escapedName}([^>]*?)\\/>`, 'g');
|
|
419
|
+
const newOutput2 = output.replace(selfPattern, () => {
|
|
420
|
+
transformed.push(compoundName);
|
|
421
|
+
return '';
|
|
422
|
+
});
|
|
423
|
+
if (newOutput2 !== output) {
|
|
424
|
+
output = newOutput2;
|
|
425
|
+
changed = true;
|
|
426
|
+
}
|
|
427
|
+
} else if (config.unwrap) {
|
|
428
|
+
// Unwrap: remove the component tags but keep children
|
|
429
|
+
// <Dialog.Body ...>children</Dialog.Body> → children
|
|
430
|
+
const pattern = new RegExp(
|
|
431
|
+
`<${escapedName}([^>]*?)>([\\s\\S]*?)<\\/${escapedName}>`,
|
|
432
|
+
'g',
|
|
433
|
+
);
|
|
434
|
+
const newOutput = output.replace(pattern, (match, attrs, children) => {
|
|
435
|
+
transformed.push(compoundName);
|
|
436
|
+
return children;
|
|
437
|
+
});
|
|
438
|
+
if (newOutput !== output) {
|
|
439
|
+
output = newOutput;
|
|
440
|
+
changed = true;
|
|
441
|
+
}
|
|
442
|
+
} else if (config.component) {
|
|
443
|
+
// Replace with new component name
|
|
444
|
+
const newComponent = config.component;
|
|
445
|
+
|
|
446
|
+
// Opening tags: <Dialog.Root> → <Dialog>
|
|
447
|
+
const openPattern = new RegExp(`<${escapedName}([^/>]*?)>`, 'g');
|
|
448
|
+
output = output.replace(openPattern, (match, attrs) => {
|
|
449
|
+
transformed.push(`${compoundName} → ${newComponent}`);
|
|
450
|
+
newComponents.add(newComponent);
|
|
451
|
+
changed = true;
|
|
452
|
+
return `<${newComponent}${attrs}>`;
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Self-closing tags: <Dialog.Root ... /> → <Dialog ... />
|
|
456
|
+
const selfPattern = new RegExp(`<${escapedName}([^>]*?)\\/>`, 'g');
|
|
457
|
+
output = output.replace(selfPattern, (match, attrs) => {
|
|
458
|
+
transformed.push(`${compoundName} → ${newComponent}`);
|
|
459
|
+
newComponents.add(newComponent);
|
|
460
|
+
changed = true;
|
|
461
|
+
return `<${newComponent}${attrs}/>`;
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Closing tags: </Dialog.Root> → </Dialog>
|
|
465
|
+
const closePattern = new RegExp(`<\\/${escapedName}>`, 'g');
|
|
466
|
+
output = output.replace(closePattern, () => {
|
|
467
|
+
changed = true;
|
|
468
|
+
return `</${newComponent}>`;
|
|
469
|
+
});
|
|
470
|
+
} else if (config.tag) {
|
|
471
|
+
// Replace with JSX tag + Tailwind classes (like regular JSX transformations)
|
|
472
|
+
const toTag = config.tag;
|
|
473
|
+
const tw = config.class || '';
|
|
474
|
+
|
|
475
|
+
// Remove Chakra props by actually parsing attributes properly
|
|
476
|
+
// This helper removes known Chakra styling props
|
|
477
|
+
function removeChakraProps(attrString) {
|
|
478
|
+
const chakraPropsSet = new Set([
|
|
479
|
+
'p',
|
|
480
|
+
'm',
|
|
481
|
+
'px',
|
|
482
|
+
'py',
|
|
483
|
+
'mx',
|
|
484
|
+
'my',
|
|
485
|
+
'pt',
|
|
486
|
+
'pb',
|
|
487
|
+
'pl',
|
|
488
|
+
'pr',
|
|
489
|
+
'mt',
|
|
490
|
+
'mb',
|
|
491
|
+
'ml',
|
|
492
|
+
'mr',
|
|
493
|
+
'w',
|
|
494
|
+
'h',
|
|
495
|
+
'bg',
|
|
496
|
+
'bgColor',
|
|
497
|
+
'color',
|
|
498
|
+
'fontSize',
|
|
499
|
+
'fontWeight',
|
|
500
|
+
'textAlign',
|
|
501
|
+
'display',
|
|
502
|
+
'flex',
|
|
503
|
+
'flexDirection',
|
|
504
|
+
'direction',
|
|
505
|
+
'align',
|
|
506
|
+
'alignItems',
|
|
507
|
+
'justify',
|
|
508
|
+
'justifyContent',
|
|
509
|
+
'gap',
|
|
510
|
+
'borderRadius',
|
|
511
|
+
'rounded',
|
|
512
|
+
'border',
|
|
513
|
+
'borderWidth',
|
|
514
|
+
'borderColor',
|
|
515
|
+
'borderStyle',
|
|
516
|
+
'boxShadow',
|
|
517
|
+
'position',
|
|
518
|
+
'top',
|
|
519
|
+
'right',
|
|
520
|
+
'bottom',
|
|
521
|
+
'left',
|
|
522
|
+
'zIndex',
|
|
523
|
+
'overflow',
|
|
524
|
+
'variant',
|
|
525
|
+
'size',
|
|
526
|
+
'colorPalette',
|
|
527
|
+
'colorScheme',
|
|
528
|
+
'spacing',
|
|
529
|
+
'maxW',
|
|
530
|
+
'maxH',
|
|
531
|
+
'minW',
|
|
532
|
+
'minH',
|
|
533
|
+
'transition',
|
|
534
|
+
'_hover',
|
|
535
|
+
'_active',
|
|
536
|
+
'_focus',
|
|
537
|
+
'_disabled',
|
|
538
|
+
'_focusVisible',
|
|
539
|
+
'status',
|
|
540
|
+
'aria',
|
|
541
|
+
'loading',
|
|
542
|
+
]);
|
|
543
|
+
|
|
544
|
+
let result = '';
|
|
545
|
+
let i = 0;
|
|
546
|
+
const str = attrString.trim();
|
|
547
|
+
|
|
548
|
+
while (i < str.length) {
|
|
549
|
+
// Skip whitespace
|
|
550
|
+
while (i < str.length && /\s/.test(str[i])) {
|
|
551
|
+
result += str[i];
|
|
552
|
+
i++;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (i >= str.length) break;
|
|
556
|
+
|
|
557
|
+
// Match attribute name
|
|
558
|
+
let name = '';
|
|
559
|
+
while (i < str.length && /[\w-]/.test(str[i])) {
|
|
560
|
+
name += str[i];
|
|
561
|
+
i++;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (!name) break;
|
|
565
|
+
|
|
566
|
+
// Skip whitespace after name
|
|
567
|
+
while (i < str.length && /\s/.test(str[i])) {
|
|
568
|
+
i++;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Check if there's a value
|
|
572
|
+
if (i < str.length && str[i] === '=') {
|
|
573
|
+
i++; // skip '='
|
|
574
|
+
|
|
575
|
+
// Skip whitespace after '='
|
|
576
|
+
while (i < str.length && /\s/.test(str[i])) {
|
|
577
|
+
i++;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Extract value
|
|
581
|
+
let value = '';
|
|
582
|
+
if (str[i] === '"' || str[i] === "'") {
|
|
583
|
+
// Quoted string
|
|
584
|
+
const quote = str[i];
|
|
585
|
+
value += str[i];
|
|
586
|
+
i++;
|
|
587
|
+
while (i < str.length && str[i] !== quote) {
|
|
588
|
+
value += str[i];
|
|
589
|
+
i++;
|
|
590
|
+
}
|
|
591
|
+
if (i < str.length) {
|
|
592
|
+
value += str[i]; // closing quote
|
|
593
|
+
i++;
|
|
594
|
+
}
|
|
595
|
+
} else if (str[i] === '{') {
|
|
596
|
+
// JSX expression - need to balance braces
|
|
597
|
+
let braceCount = 0;
|
|
598
|
+
while (i < str.length) {
|
|
599
|
+
if (str[i] === '{') braceCount++;
|
|
600
|
+
if (str[i] === '}') braceCount--;
|
|
601
|
+
value += str[i];
|
|
602
|
+
i++;
|
|
603
|
+
if (braceCount === 0) break;
|
|
604
|
+
}
|
|
605
|
+
} else {
|
|
606
|
+
// Unquoted value (boolean shorthand)
|
|
607
|
+
while (i < str.length && /[\w.-]/.test(str[i])) {
|
|
608
|
+
value += str[i];
|
|
609
|
+
i++;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Only keep if not a Chakra prop
|
|
614
|
+
if (!chakraPropsSet.has(name)) {
|
|
615
|
+
result += name + '=' + value;
|
|
616
|
+
}
|
|
617
|
+
} else {
|
|
618
|
+
// Boolean prop (no value)
|
|
619
|
+
if (!chakraPropsSet.has(name)) {
|
|
620
|
+
result += name;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Self-closing tags: <Card.Root ... /> → <div className="..." />
|
|
629
|
+
const selfPattern = new RegExp(`<${escapedName}([^>]*?)\\/>`, 'g');
|
|
630
|
+
output = output.replace(selfPattern, (match, attrs) => {
|
|
631
|
+
// Remove Chakra props
|
|
632
|
+
let cleanedAttrs = removeChakraProps(attrs);
|
|
633
|
+
|
|
634
|
+
// Add Tailwind classes
|
|
635
|
+
const tailwindClasses = tw ? tw.split(' ').filter(Boolean) : [];
|
|
636
|
+
let newAttrs = '';
|
|
637
|
+
|
|
638
|
+
if (cleanedAttrs.trim() || tailwindClasses.length > 0) {
|
|
639
|
+
const attrsToKeep = cleanedAttrs.trim();
|
|
640
|
+
if (tailwindClasses.length > 0) {
|
|
641
|
+
if (attrsToKeep) {
|
|
642
|
+
newAttrs = ` ${attrsToKeep} className="${tailwindClasses.join(' ')}"`;
|
|
643
|
+
} else {
|
|
644
|
+
newAttrs = ` className="${tailwindClasses.join(' ')}"`;
|
|
645
|
+
}
|
|
646
|
+
} else if (attrsToKeep) {
|
|
647
|
+
newAttrs = ` ${attrsToKeep}`;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
transformed.push(`${compoundName} → ${toTag}`);
|
|
652
|
+
changed = true;
|
|
653
|
+
return `<${toTag}${newAttrs}/>`;
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Opening tags: <Card.Root> → <div className="...">
|
|
657
|
+
const openPattern = new RegExp(`<${escapedName}([^/>]*?)>`, 'g');
|
|
658
|
+
output = output.replace(openPattern, (match, attrs) => {
|
|
659
|
+
// Remove Chakra props
|
|
660
|
+
let cleanedAttrs = removeChakraProps(attrs);
|
|
661
|
+
|
|
662
|
+
// Add Tailwind classes
|
|
663
|
+
const tailwindClasses = tw ? tw.split(' ').filter(Boolean) : [];
|
|
664
|
+
let newAttrs = '';
|
|
665
|
+
|
|
666
|
+
if (cleanedAttrs.trim() || tailwindClasses.length > 0) {
|
|
667
|
+
const attrsToKeep = cleanedAttrs.trim();
|
|
668
|
+
if (tailwindClasses.length > 0) {
|
|
669
|
+
if (attrsToKeep) {
|
|
670
|
+
newAttrs = ` ${attrsToKeep} className="${tailwindClasses.join(' ')}"`;
|
|
671
|
+
} else {
|
|
672
|
+
newAttrs = ` className="${tailwindClasses.join(' ')}"`;
|
|
673
|
+
}
|
|
674
|
+
} else if (attrsToKeep) {
|
|
675
|
+
newAttrs = ` ${attrsToKeep}`;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
transformed.push(`${compoundName} → ${toTag}`);
|
|
680
|
+
changed = true;
|
|
681
|
+
return `<${toTag}${newAttrs}>`;
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// Closing tags: </Card.Root> → </div>
|
|
685
|
+
const closePattern = new RegExp(`<\\/${escapedName}>`, 'g');
|
|
686
|
+
output = output.replace(closePattern, () => {
|
|
687
|
+
changed = true;
|
|
688
|
+
return `</${toTag}>`;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return {
|
|
694
|
+
output,
|
|
695
|
+
changed,
|
|
696
|
+
transformed,
|
|
697
|
+
newComponents: Array.from(newComponents),
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function transformJSX(input, jsxSpecifiers, usedSpecifiers) {
|
|
702
|
+
if (!input) return { output: input, changed: false, replaced: [] };
|
|
703
|
+
let output = input;
|
|
704
|
+
const replaced = new Set();
|
|
705
|
+
let changed = false;
|
|
706
|
+
|
|
707
|
+
// Chakra prop to Tailwind class mappings
|
|
708
|
+
const PROP_MAPPINGS = {
|
|
709
|
+
// Spacing
|
|
710
|
+
p: (v) => `p-${v}`,
|
|
711
|
+
px: (v) => `px-${v}`,
|
|
712
|
+
py: (v) => `py-${v}`,
|
|
713
|
+
pt: (v) => `pt-${v}`,
|
|
714
|
+
pb: (v) => `pb-${v}`,
|
|
715
|
+
pl: (v) => `pl-${v}`,
|
|
716
|
+
pr: (v) => `pr-${v}`,
|
|
717
|
+
m: (v) => `m-${v}`,
|
|
718
|
+
mx: (v) => `mx-${v}`,
|
|
719
|
+
my: (v) => `my-${v}`,
|
|
720
|
+
mt: (v) => `mt-${v}`,
|
|
721
|
+
mb: (v) => `mb-${v}`,
|
|
722
|
+
ml: (v) => `ml-${v}`,
|
|
723
|
+
mr: (v) => `mr-${v}`,
|
|
724
|
+
gap: (v) => `gap-${v}`,
|
|
725
|
+
|
|
726
|
+
// Sizing
|
|
727
|
+
w: (v) => (v === 'full' ? 'w-full' : `w-[${v}]`),
|
|
728
|
+
h: (v) => (v === 'full' ? 'h-full' : `h-[${v}]`),
|
|
729
|
+
width: (v) => (v === 'full' ? 'w-full' : `w-[${v}]`),
|
|
730
|
+
height: (v) => (v === 'full' ? 'h-full' : `h-[${v}]`),
|
|
731
|
+
maxW: (v) => (v === 'full' ? 'max-w-full' : `max-w-[${v}]`),
|
|
732
|
+
maxH: (v) => (v === 'full' ? 'max-h-full' : `max-h-[${v}]`),
|
|
733
|
+
minW: (v) => `min-w-[${v}]`,
|
|
734
|
+
minH: (v) => `min-h-[${v}]`,
|
|
735
|
+
|
|
736
|
+
// Flexbox
|
|
737
|
+
flex: (v) => (v === '1' || v === 1 ? 'flex-1' : `flex-[${v}]`),
|
|
738
|
+
flexDir: (v) =>
|
|
739
|
+
v === 'column' ? 'flex-col' : v === 'row' ? 'flex-row' : `flex-${v}`,
|
|
740
|
+
flexDirection: (v) =>
|
|
741
|
+
v === 'column' ? 'flex-col' : v === 'row' ? 'flex-row' : `flex-${v}`,
|
|
742
|
+
direction: (v) =>
|
|
743
|
+
v === 'column' ? 'flex-col' : v === 'row' ? 'flex-row' : `flex-${v}`,
|
|
744
|
+
align: (v) =>
|
|
745
|
+
v === 'center'
|
|
746
|
+
? 'items-center'
|
|
747
|
+
: v === 'start'
|
|
748
|
+
? 'items-start'
|
|
749
|
+
: v === 'end'
|
|
750
|
+
? 'items-end'
|
|
751
|
+
: `items-${v}`,
|
|
752
|
+
alignItems: (v) =>
|
|
753
|
+
v === 'center'
|
|
754
|
+
? 'items-center'
|
|
755
|
+
: v === 'start'
|
|
756
|
+
? 'items-start'
|
|
757
|
+
: v === 'end'
|
|
758
|
+
? 'items-end'
|
|
759
|
+
: `items-${v}`,
|
|
760
|
+
justify: (v) =>
|
|
761
|
+
v === 'center'
|
|
762
|
+
? 'justify-center'
|
|
763
|
+
: v === 'between'
|
|
764
|
+
? 'justify-between'
|
|
765
|
+
: v === 'around'
|
|
766
|
+
? 'justify-around'
|
|
767
|
+
: `justify-${v}`,
|
|
768
|
+
justifyContent: (v) =>
|
|
769
|
+
v === 'center'
|
|
770
|
+
? 'justify-center'
|
|
771
|
+
: v === 'between'
|
|
772
|
+
? 'justify-between'
|
|
773
|
+
: v === 'around'
|
|
774
|
+
? 'justify-around'
|
|
775
|
+
: `justify-${v}`,
|
|
776
|
+
|
|
777
|
+
// Display
|
|
778
|
+
display: (v) => v,
|
|
779
|
+
|
|
780
|
+
// Position
|
|
781
|
+
pos: (v) => v,
|
|
782
|
+
position: (v) => v,
|
|
783
|
+
|
|
784
|
+
// Overflow
|
|
785
|
+
overflow: (v) => `overflow-${v}`,
|
|
786
|
+
overflowX: (v) => `overflow-x-${v}`,
|
|
787
|
+
overflowY: (v) => `overflow-y-${v}`,
|
|
788
|
+
|
|
789
|
+
// Text
|
|
790
|
+
fontSize: (v) => `text-[${v}]`,
|
|
791
|
+
fontWeight: (v) =>
|
|
792
|
+
v === 'bold'
|
|
793
|
+
? 'font-bold'
|
|
794
|
+
: v === 'semibold'
|
|
795
|
+
? 'font-semibold'
|
|
796
|
+
: `font-${v}`,
|
|
797
|
+
textAlign: (v) => `text-${v}`,
|
|
798
|
+
color: (v) => `text-[${v}]`,
|
|
799
|
+
|
|
800
|
+
// Background
|
|
801
|
+
bg: (v) => `bg-[${v}]`,
|
|
802
|
+
bgColor: (v) => `bg-[${v}]`,
|
|
803
|
+
|
|
804
|
+
// Border
|
|
805
|
+
border: (v) => `border-[${v}]`,
|
|
806
|
+
borderLeft: (v) => `border-l-[${v}]`,
|
|
807
|
+
borderRight: (v) => `border-r-[${v}]`,
|
|
808
|
+
borderTop: (v) => `border-t-[${v}]`,
|
|
809
|
+
borderBottom: (v) => `border-b-[${v}]`,
|
|
810
|
+
borderColor: (v) => `border-[${v}]`,
|
|
811
|
+
borderRadius: (v) =>
|
|
812
|
+
v === 'sm'
|
|
813
|
+
? 'rounded-sm'
|
|
814
|
+
: v === 'md'
|
|
815
|
+
? 'rounded-md'
|
|
816
|
+
: v === 'lg'
|
|
817
|
+
? 'rounded-lg'
|
|
818
|
+
: `rounded-[${v}]`,
|
|
819
|
+
rounded: (v) =>
|
|
820
|
+
v === 'sm'
|
|
821
|
+
? 'rounded-sm'
|
|
822
|
+
: v === 'md'
|
|
823
|
+
? 'rounded-md'
|
|
824
|
+
: v === 'lg'
|
|
825
|
+
? 'rounded-lg'
|
|
826
|
+
: `rounded-[${v}]`,
|
|
827
|
+
|
|
828
|
+
// More flexbox
|
|
829
|
+
flexWrap: (v) => `flex-wrap${v === 'wrap' ? '' : `-${v}`}`,
|
|
830
|
+
flexShrink: (v) => `flex-shrink${v === '0' || v === 0 ? '-0' : ''}`,
|
|
831
|
+
flexGrow: (v) => `flex-grow${v === '1' || v === 1 ? '' : `-${v}`}`,
|
|
832
|
+
|
|
833
|
+
// Grid
|
|
834
|
+
columns: (v) => `grid-cols-${v}`,
|
|
835
|
+
rows: (v) => `grid-rows-${v}`,
|
|
836
|
+
|
|
837
|
+
// Opacity
|
|
838
|
+
opacity: (v) => `opacity-${Math.round(parseFloat(v) * 100)}`,
|
|
839
|
+
|
|
840
|
+
// Min/Max
|
|
841
|
+
minHeight: (v) => `min-h-[${v}]`,
|
|
842
|
+
minWidth: (v) => `min-w-[${v}]`,
|
|
843
|
+
|
|
844
|
+
// Text
|
|
845
|
+
lineClamp: (v) => `line-clamp-${v}`,
|
|
846
|
+
lineHeight: (v) => `leading-[${v}]`,
|
|
847
|
+
whiteSpace: (v) => `whitespace-${v}`,
|
|
848
|
+
|
|
849
|
+
// Border colors
|
|
850
|
+
borderLeftColor: (v) => `border-l-[${v}]`,
|
|
851
|
+
borderRightColor: (v) => `border-r-[${v}]`,
|
|
852
|
+
borderTopColor: (v) => `border-t-[${v}]`,
|
|
853
|
+
borderBottomColor: (v) => `border-b-[${v}]`,
|
|
854
|
+
|
|
855
|
+
// Misc
|
|
856
|
+
padding: (v) => `p-${v}`,
|
|
857
|
+
wrap: (v) => `flex-wrap${v === 'wrap' ? '' : `-${v}`}`,
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
// Component-specific prop mappings
|
|
861
|
+
const COMPONENT_SPECIFIC_PROPS = {
|
|
862
|
+
Heading: {
|
|
863
|
+
size: {
|
|
864
|
+
xs: 'text-md',
|
|
865
|
+
sm: 'text-lg',
|
|
866
|
+
md: 'text-xl',
|
|
867
|
+
lg: 'text-2xl',
|
|
868
|
+
xl: 'text-3xl',
|
|
869
|
+
'2xl': 'text-4xl',
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
Text: {
|
|
873
|
+
size: {
|
|
874
|
+
xs: 'text-xs',
|
|
875
|
+
sm: 'text-sm',
|
|
876
|
+
md: 'text-base',
|
|
877
|
+
lg: 'text-lg',
|
|
878
|
+
xl: 'text-xl',
|
|
879
|
+
'2xl': 'text-2xl',
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
// Parse JSX attributes from a string
|
|
885
|
+
function parseAttributes(attrString) {
|
|
886
|
+
const attrs = [];
|
|
887
|
+
let remaining = attrString.trim();
|
|
888
|
+
|
|
889
|
+
while (remaining) {
|
|
890
|
+
// Skip leading whitespace
|
|
891
|
+
remaining = remaining.replace(/^\s+/, '');
|
|
892
|
+
if (!remaining) break;
|
|
893
|
+
|
|
894
|
+
// Match attribute name (including hyphens for aria-*, data-*, etc.)
|
|
895
|
+
const nameMatch = remaining.match(/^([\w-]+)/);
|
|
896
|
+
if (!nameMatch) break;
|
|
897
|
+
|
|
898
|
+
const name = nameMatch[1];
|
|
899
|
+
remaining = remaining.slice(name.length);
|
|
900
|
+
|
|
901
|
+
// Check if there's a value
|
|
902
|
+
if (remaining.match(/^\s*=/)) {
|
|
903
|
+
remaining = remaining.replace(/^\s*=\s*/, '');
|
|
904
|
+
|
|
905
|
+
let value = null;
|
|
906
|
+
let isJSX = false;
|
|
907
|
+
let fullMatch = name + '=';
|
|
908
|
+
|
|
909
|
+
if (remaining[0] === '{') {
|
|
910
|
+
// JSX expression - need to balance braces
|
|
911
|
+
let depth = 0;
|
|
912
|
+
let i = 0;
|
|
913
|
+
for (; i < remaining.length; i++) {
|
|
914
|
+
if (remaining[i] === '{') depth++;
|
|
915
|
+
if (remaining[i] === '}') depth--;
|
|
916
|
+
if (depth === 0) break;
|
|
917
|
+
}
|
|
918
|
+
value = remaining.slice(1, i);
|
|
919
|
+
fullMatch += remaining.slice(0, i + 1);
|
|
920
|
+
remaining = remaining.slice(i + 1);
|
|
921
|
+
isJSX = true;
|
|
922
|
+
} else if (remaining[0] === '"') {
|
|
923
|
+
// Double quoted string
|
|
924
|
+
const endQuote = remaining.indexOf('"', 1);
|
|
925
|
+
value = remaining.slice(1, endQuote);
|
|
926
|
+
fullMatch += remaining.slice(0, endQuote + 1);
|
|
927
|
+
remaining = remaining.slice(endQuote + 1);
|
|
928
|
+
} else if (remaining[0] === "'") {
|
|
929
|
+
// Single quoted string
|
|
930
|
+
const endQuote = remaining.indexOf("'", 1);
|
|
931
|
+
value = remaining.slice(1, endQuote);
|
|
932
|
+
fullMatch += remaining.slice(0, endQuote + 1);
|
|
933
|
+
remaining = remaining.slice(endQuote + 1);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
attrs.push({ name, value, isJSX, fullMatch });
|
|
937
|
+
} else {
|
|
938
|
+
// Boolean attribute
|
|
939
|
+
attrs.push({ name, value: true, isJSX: false, fullMatch: name });
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return attrs;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Transform Chakra props to Tailwind classes
|
|
947
|
+
function transformChakraProps(attrString, componentName) {
|
|
948
|
+
const attrs = parseAttributes(attrString);
|
|
949
|
+
const tailwindClasses = [];
|
|
950
|
+
const keepAttrs = [];
|
|
951
|
+
let asElement = null;
|
|
952
|
+
|
|
953
|
+
for (const attr of attrs) {
|
|
954
|
+
if (attr.name === 'className' || attr.name === 'class') {
|
|
955
|
+
// Keep className as-is, we'll merge with it later
|
|
956
|
+
keepAttrs.push(attr);
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Skip Chakra pseudo-props (require manual conversion)
|
|
961
|
+
if (attr.name.startsWith('_')) {
|
|
962
|
+
// _hover, _active, _focus, etc. - skip these (will be removed)
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Keep 'as' prop for now - it will cause type errors but won't break JSX
|
|
967
|
+
// TODO: Handle polymorphic 'as' prop transformation
|
|
968
|
+
if (attr.name === 'as') {
|
|
969
|
+
keepAttrs.push(attr);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Keep data and aria attributes
|
|
974
|
+
if (attr.name.startsWith('data-') || attr.name.startsWith('aria-')) {
|
|
975
|
+
keepAttrs.push(attr);
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// Check for component-specific prop handling
|
|
980
|
+
const componentProps = COMPONENT_SPECIFIC_PROPS[componentName];
|
|
981
|
+
if (componentProps && componentProps[attr.name]) {
|
|
982
|
+
// Component-specific transformation
|
|
983
|
+
try {
|
|
984
|
+
let value = attr.value;
|
|
985
|
+
if (attr.isJSX) {
|
|
986
|
+
value = value.trim().replace(/^["']|["']$/g, '');
|
|
987
|
+
}
|
|
988
|
+
const mapping = componentProps[attr.name];
|
|
989
|
+
const twClass = mapping[value] || mapping[value.toString()];
|
|
990
|
+
if (twClass) {
|
|
991
|
+
tailwindClasses.push(twClass);
|
|
992
|
+
continue; // Don't keep the original attribute
|
|
993
|
+
}
|
|
994
|
+
} catch {
|
|
995
|
+
// Fall through to default handling
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const mapper = PROP_MAPPINGS[attr.name];
|
|
1000
|
+
if (mapper) {
|
|
1001
|
+
// Transform Chakra prop to Tailwind
|
|
1002
|
+
try {
|
|
1003
|
+
let value = attr.value;
|
|
1004
|
+
// Handle JSX expressions: extract the value if it's a simple literal
|
|
1005
|
+
if (attr.isJSX) {
|
|
1006
|
+
value = value.trim();
|
|
1007
|
+
// Remove quotes if present
|
|
1008
|
+
value = value.replace(/^["']|["']$/g, '');
|
|
1009
|
+
}
|
|
1010
|
+
const twClass = mapper(value);
|
|
1011
|
+
if (twClass) {
|
|
1012
|
+
tailwindClasses.push(twClass);
|
|
1013
|
+
}
|
|
1014
|
+
} catch {
|
|
1015
|
+
// If transformation fails, keep the original attribute
|
|
1016
|
+
keepAttrs.push(attr);
|
|
1017
|
+
}
|
|
1018
|
+
} else {
|
|
1019
|
+
// Keep attributes we don't recognize
|
|
1020
|
+
keepAttrs.push(attr);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return { tailwindClasses, keepAttrs, asElement };
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Reconstruct attribute string from parsed attributes
|
|
1028
|
+
function reconstructAttrs(keepAttrs, tailwindClasses) {
|
|
1029
|
+
const parts = [];
|
|
1030
|
+
|
|
1031
|
+
// Add kept attributes
|
|
1032
|
+
for (const attr of keepAttrs) {
|
|
1033
|
+
if (attr.name === 'className' || attr.name === 'class') {
|
|
1034
|
+
// Merge Tailwind classes with existing className
|
|
1035
|
+
if (tailwindClasses.length > 0) {
|
|
1036
|
+
const existingClasses = attr.isJSX ? attr.value : attr.value;
|
|
1037
|
+
if (attr.isJSX) {
|
|
1038
|
+
// className={...} - merge into template literal
|
|
1039
|
+
parts.push(
|
|
1040
|
+
`className={\`${tailwindClasses.join(' ')} \${${existingClasses}}\`}`,
|
|
1041
|
+
);
|
|
1042
|
+
} else {
|
|
1043
|
+
// className="..." - simple string concatenation
|
|
1044
|
+
parts.push(
|
|
1045
|
+
`className="${tailwindClasses.join(' ')} ${existingClasses}"`,
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
tailwindClasses.length = 0; // Clear so we don't add again
|
|
1049
|
+
} else {
|
|
1050
|
+
parts.push(attr.fullMatch);
|
|
1051
|
+
}
|
|
1052
|
+
} else {
|
|
1053
|
+
parts.push(attr.fullMatch);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// If we have Tailwind classes but no existing className, add it
|
|
1058
|
+
if (tailwindClasses.length > 0) {
|
|
1059
|
+
parts.push(`className="${tailwindClasses.join(' ')}"`);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
return parts.length > 0 ? ' ' + parts.join(' ') : '';
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// Inject Tailwind classes into className attribute (legacy - kept for backward compat)
|
|
1066
|
+
// legacy helper removed (unused)
|
|
1067
|
+
|
|
1068
|
+
for (const [fromName, spec] of Object.entries(jsxSpecifiers)) {
|
|
1069
|
+
if (usedSpecifiers && usedSpecifiers.size && !usedSpecifiers.has(fromName))
|
|
1070
|
+
continue;
|
|
1071
|
+
const toTag = spec.tag || 'div';
|
|
1072
|
+
const tw = spec.class || '';
|
|
1073
|
+
const escapedFromName = escapeRegex(fromName);
|
|
1074
|
+
|
|
1075
|
+
// Self-closing tags: <From ... />
|
|
1076
|
+
// Use negative lookbehind to avoid matching TypeScript generics (e.g., Array<Text>)
|
|
1077
|
+
// JSX tags are preceded by: =, (, {, >, whitespace, or start of string
|
|
1078
|
+
const selfClosing = new RegExp(
|
|
1079
|
+
`(?<=[=({>\\s]|^)<${escapedFromName}([^>]*?)\\/>`,
|
|
1080
|
+
'gs',
|
|
1081
|
+
);
|
|
1082
|
+
output = output.replace(selfClosing, (match, attrs) => {
|
|
1083
|
+
// Transform Chakra props to Tailwind classes, passing component name for context
|
|
1084
|
+
const { tailwindClasses, keepAttrs } = transformChakraProps(
|
|
1085
|
+
attrs,
|
|
1086
|
+
fromName,
|
|
1087
|
+
);
|
|
1088
|
+
// Add component-specific Tailwind classes from config
|
|
1089
|
+
if (tw) {
|
|
1090
|
+
tailwindClasses.unshift(...tw.split(' ').filter(Boolean));
|
|
1091
|
+
}
|
|
1092
|
+
// Reconstruct attributes with transformed props
|
|
1093
|
+
let newAttrs = reconstructAttrs(keepAttrs, tailwindClasses);
|
|
1094
|
+
|
|
1095
|
+
changed = true;
|
|
1096
|
+
replaced.add(fromName);
|
|
1097
|
+
return `<${toTag}${newAttrs}/>`;
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
// Opening tags: <From ...>
|
|
1101
|
+
// Use negative lookbehind to avoid matching TypeScript generics (e.g., React.FC<TextProps>)
|
|
1102
|
+
// JSX tags are preceded by: =, (, {, >, whitespace, or start of string
|
|
1103
|
+
const opening = new RegExp(
|
|
1104
|
+
`(?<=[=({>\\s]|^)<${escapedFromName}([^>]*?)>`,
|
|
1105
|
+
'gs',
|
|
1106
|
+
);
|
|
1107
|
+
output = output.replace(opening, (match, attrs) => {
|
|
1108
|
+
// Transform Chakra props to Tailwind classes, passing component name for context
|
|
1109
|
+
const { tailwindClasses, keepAttrs } = transformChakraProps(
|
|
1110
|
+
attrs,
|
|
1111
|
+
fromName,
|
|
1112
|
+
);
|
|
1113
|
+
// Add component-specific Tailwind classes from config
|
|
1114
|
+
if (tw) {
|
|
1115
|
+
tailwindClasses.unshift(...tw.split(' ').filter(Boolean));
|
|
1116
|
+
}
|
|
1117
|
+
// Reconstruct attributes with transformed props
|
|
1118
|
+
let newAttrs = reconstructAttrs(keepAttrs, tailwindClasses);
|
|
1119
|
+
|
|
1120
|
+
changed = true;
|
|
1121
|
+
replaced.add(fromName);
|
|
1122
|
+
return `<${toTag}${newAttrs}>`;
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
// Closing tags: </From>
|
|
1126
|
+
const closing = new RegExp(`<\\/${escapedFromName}\\s*>`, 'g');
|
|
1127
|
+
output = output.replace(closing, () => {
|
|
1128
|
+
changed = true;
|
|
1129
|
+
replaced.add(fromName);
|
|
1130
|
+
return `</${toTag}>`;
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
return { output, changed, replaced };
|
|
1135
|
+
}
|