@dialpad/dialtone-css 8.78.0-next.2 → 8.78.0-next.3
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/lib/build/js/dialtone_migrate_tshirt_to_numeric/index.mjs +233 -0
- package/lib/build/js/dialtone_migrate_tshirt_to_numeric/test.mjs +336 -0
- package/lib/build/js/dialtone_migration_helper/configs/physical-to-logical.mjs +86 -0
- package/lib/dist/js/dialtone_migrate_tshirt_to_numeric/index.mjs +233 -0
- package/lib/dist/js/dialtone_migrate_tshirt_to_numeric/test.mjs +336 -0
- package/lib/dist/js/dialtone_migration_helper/configs/physical-to-logical.mjs +86 -0
- package/package.json +4 -4
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Migration script to convert t-shirt size props to numeric scale on Dialtone components.
|
|
5
|
+
*
|
|
6
|
+
* Transforms: size="sm" → :size="200", label-size="xs" → :label-size="100", speed="md" → :speed="300"
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx dialtone-migrate-tshirt-to-numeric [options]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* --cwd <path> Working directory (default: current directory)
|
|
13
|
+
* --dry-run Show changes without applying them
|
|
14
|
+
* --yes Apply all changes without prompting
|
|
15
|
+
* --help Show help
|
|
16
|
+
*
|
|
17
|
+
* Examples:
|
|
18
|
+
* npx dialtone-migrate-tshirt-to-numeric
|
|
19
|
+
* npx dialtone-migrate-tshirt-to-numeric --dry-run
|
|
20
|
+
* npx dialtone-migrate-tshirt-to-numeric --cwd ./src
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'fs/promises';
|
|
24
|
+
import { realpathSync } from 'node:fs';
|
|
25
|
+
import path from 'path';
|
|
26
|
+
import readline from 'readline';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Mapping
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
const SIZE_MAP = {
|
|
34
|
+
xs: '100',
|
|
35
|
+
sm: '200',
|
|
36
|
+
md: '300',
|
|
37
|
+
lg: '400',
|
|
38
|
+
xl: '500',
|
|
39
|
+
'2xl': '600',
|
|
40
|
+
'3xl': '700',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const TSHIRT_VALUES = Object.keys(SIZE_MAP).join('|');
|
|
44
|
+
|
|
45
|
+
// Match any prop ending in size/Size/speed/Speed with a t-shirt value.
|
|
46
|
+
// In the replacer, check that the character before the match is NOT a colon (v-bind).
|
|
47
|
+
const PROP_REGEX = new RegExp(
|
|
48
|
+
`([\\w-]*(?:[Ss]ize|[Ss]peed))="(${TSHIRT_VALUES})"`,
|
|
49
|
+
'g',
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Props that end in "size" but are NOT component scale sizes — exclude from migration
|
|
53
|
+
const EXCLUDED_PROPS = ['button-width-size', 'buttonWidthSize', 'background-size', 'backgroundSize', 'font-size', 'fontSize'];
|
|
54
|
+
|
|
55
|
+
// Only match on Dialtone component tags
|
|
56
|
+
// Use [\s\S] instead of [^>] to match across newlines in multiline tags
|
|
57
|
+
const DT_TAG_PATTERN = /<(dt-[\w-]+|Dt\w+)\b[\s\S]*?>/g;
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// File finder
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
async function findFiles (dir, extensions, ignore = []) {
|
|
64
|
+
const results = [];
|
|
65
|
+
|
|
66
|
+
async function walk (currentDir) {
|
|
67
|
+
try {
|
|
68
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
71
|
+
if (ignore.some(ig => fullPath.includes(ig))) continue;
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
await walk(fullPath);
|
|
74
|
+
} else if (entry.isFile()) {
|
|
75
|
+
if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
76
|
+
results.push(fullPath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// Skip unreadable directories
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await walk(dir);
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Transform logic
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
function transformContent (content) {
|
|
94
|
+
let transformed = content;
|
|
95
|
+
let count = 0;
|
|
96
|
+
|
|
97
|
+
// Replace t-shirt sizes only within Dialtone component tags
|
|
98
|
+
transformed = transformed.replace(DT_TAG_PATTERN, (tag) => {
|
|
99
|
+
PROP_REGEX.lastIndex = 0;
|
|
100
|
+
return tag.replace(PROP_REGEX, (match, propName, tshirt, offset, fullTag) => {
|
|
101
|
+
// Skip if preceded by ':' (already a v-bind expression)
|
|
102
|
+
if (offset > 0 && fullTag[offset - 1] === ':') return match;
|
|
103
|
+
// Skip excluded prop names (not component scale sizes)
|
|
104
|
+
if (EXCLUDED_PROPS.includes(propName)) return match;
|
|
105
|
+
if (SIZE_MAP[tshirt]) {
|
|
106
|
+
count++;
|
|
107
|
+
return `:${propName}="${SIZE_MAP[tshirt]}"`;
|
|
108
|
+
}
|
|
109
|
+
return match;
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return { transformed, count };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { transformContent, SIZE_MAP };
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// CLI
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
function printHelp () {
|
|
123
|
+
console.log(`
|
|
124
|
+
Usage: npx dialtone-migrate-tshirt-to-numeric [options]
|
|
125
|
+
|
|
126
|
+
Converts t-shirt size props to numeric scale on Dialtone components.
|
|
127
|
+
|
|
128
|
+
size="sm" → :size="200"
|
|
129
|
+
label-size="xs" → :label-size="100"
|
|
130
|
+
speed="md" → :speed="300"
|
|
131
|
+
|
|
132
|
+
Options:
|
|
133
|
+
--cwd <path> Working directory (default: current directory)
|
|
134
|
+
--dry-run Show changes without applying them
|
|
135
|
+
--yes Apply all changes without prompting
|
|
136
|
+
--help Show help
|
|
137
|
+
|
|
138
|
+
Size mapping:
|
|
139
|
+
xs → 100 sm → 200 md → 300 lg → 400 xl → 500
|
|
140
|
+
2xl → 600 3xl → 700
|
|
141
|
+
`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function prompt (question) {
|
|
145
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
146
|
+
return new Promise(resolve => {
|
|
147
|
+
rl.question(question, answer => {
|
|
148
|
+
rl.close();
|
|
149
|
+
resolve(answer.trim().toLowerCase());
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// eslint-disable-next-line complexity
|
|
155
|
+
async function main () {
|
|
156
|
+
const args = process.argv.slice(2);
|
|
157
|
+
|
|
158
|
+
if (args.includes('--help')) {
|
|
159
|
+
printHelp();
|
|
160
|
+
process.exit(0);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const dryRun = args.includes('--dry-run');
|
|
164
|
+
const autoYes = args.includes('--yes');
|
|
165
|
+
const cwdIndex = args.indexOf('--cwd');
|
|
166
|
+
const cwd = cwdIndex !== -1 && args[cwdIndex + 1]
|
|
167
|
+
? path.resolve(args[cwdIndex + 1])
|
|
168
|
+
: process.cwd();
|
|
169
|
+
|
|
170
|
+
console.log(`\nScanning ${cwd} for t-shirt size usage on Dialtone components...\n`);
|
|
171
|
+
|
|
172
|
+
const extensions = ['.vue', '.md', '.html', '.js', '.ts', '.jsx', '.tsx'];
|
|
173
|
+
const ignore = ['node_modules', 'dist', '.git', '.vuepress/public'];
|
|
174
|
+
const files = await findFiles(cwd, extensions, ignore);
|
|
175
|
+
|
|
176
|
+
const changes = [];
|
|
177
|
+
|
|
178
|
+
for (const file of files) {
|
|
179
|
+
const content = await fs.readFile(file, 'utf8');
|
|
180
|
+
const { transformed, count } = transformContent(content);
|
|
181
|
+
if (count > 0) {
|
|
182
|
+
changes.push({ file, content, transformed, count });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (changes.length === 0) {
|
|
187
|
+
console.log('No t-shirt size usage found. Nothing to migrate.');
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(`Found ${changes.reduce((sum, c) => sum + c.count, 0)} t-shirt size references across ${changes.length} files:\n`);
|
|
192
|
+
|
|
193
|
+
for (const { file, count } of changes) {
|
|
194
|
+
const rel = path.relative(cwd, file);
|
|
195
|
+
console.log(` ${rel} (${count} change${count > 1 ? 's' : ''})`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (dryRun) {
|
|
199
|
+
console.log('\n--dry-run: No files were modified.\n');
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!autoYes) {
|
|
204
|
+
const answer = await prompt('\nApply changes? (y/N) ');
|
|
205
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
206
|
+
console.log('Cancelled.');
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const { file, transformed } of changes) {
|
|
212
|
+
await fs.writeFile(file, transformed, 'utf8');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.log(`\nMigrated ${changes.reduce((sum, c) => sum + c.count, 0)} references across ${changes.length} files.\n`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Only run CLI when executed directly (not when imported for testing).
|
|
219
|
+
// Uses realpathSync to resolve symlinks from npx/npm bin shims.
|
|
220
|
+
const isDirectRun = (() => {
|
|
221
|
+
try {
|
|
222
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
})();
|
|
227
|
+
|
|
228
|
+
if (isDirectRun) {
|
|
229
|
+
main().catch(err => {
|
|
230
|
+
console.error(err);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Tests for dialtone-migrate-tshirt-to-numeric codemod.
|
|
5
|
+
* Run: node packages/dialtone-css/lib/build/js/dialtone_migrate_tshirt_to_numeric/test.mjs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { describe, it } from 'node:test';
|
|
10
|
+
import { transformContent } from './index.mjs';
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Tests
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
describe('Basic size prop transforms', () => {
|
|
17
|
+
it('transforms size="xs" to :size="100"', () => {
|
|
18
|
+
const input = '<dt-button size="xs">Click</dt-button>';
|
|
19
|
+
const expected = '<dt-button :size="100">Click</dt-button>';
|
|
20
|
+
const { transformed, count } = transformContent(input);
|
|
21
|
+
assert.equal(transformed, expected);
|
|
22
|
+
assert.equal(count, 1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('transforms size="sm" to :size="200"', () => {
|
|
26
|
+
const input = '<dt-text size="sm">Hello</dt-text>';
|
|
27
|
+
const expected = '<dt-text :size="200">Hello</dt-text>';
|
|
28
|
+
const { transformed } = transformContent(input);
|
|
29
|
+
assert.equal(transformed, expected);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('transforms size="md" to :size="300"', () => {
|
|
33
|
+
const input = '<dt-input size="md" label="Name" />';
|
|
34
|
+
const expected = '<dt-input :size="300" label="Name" />';
|
|
35
|
+
const { transformed } = transformContent(input);
|
|
36
|
+
assert.equal(transformed, expected);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('transforms size="lg" to :size="400"', () => {
|
|
40
|
+
const input = '<dt-toggle size="lg" />';
|
|
41
|
+
const expected = '<dt-toggle :size="400" />';
|
|
42
|
+
const { transformed } = transformContent(input);
|
|
43
|
+
assert.equal(transformed, expected);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('transforms size="xl" to :size="500"', () => {
|
|
47
|
+
const input = '<dt-segmented-control size="xl" />';
|
|
48
|
+
const expected = '<dt-segmented-control :size="500" />';
|
|
49
|
+
const { transformed } = transformContent(input);
|
|
50
|
+
assert.equal(transformed, expected);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('transforms size="2xl" to :size="600"', () => {
|
|
54
|
+
const input = '<dt-text kind="headline" size="2xl">Title</dt-text>';
|
|
55
|
+
const expected = '<dt-text kind="headline" :size="600">Title</dt-text>';
|
|
56
|
+
const { transformed } = transformContent(input);
|
|
57
|
+
assert.equal(transformed, expected);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('transforms size="3xl" to :size="700"', () => {
|
|
61
|
+
const input = '<dt-text kind="headline" size="3xl">Title</dt-text>';
|
|
62
|
+
const expected = '<dt-text kind="headline" :size="700">Title</dt-text>';
|
|
63
|
+
const { transformed } = transformContent(input);
|
|
64
|
+
assert.equal(transformed, expected);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('label-size prop transforms', () => {
|
|
69
|
+
it('transforms label-size="xs" to :label-size="100"', () => {
|
|
70
|
+
const input = '<dt-input label-size="xs" label="Name" />';
|
|
71
|
+
const expected = '<dt-input :label-size="100" label="Name" />';
|
|
72
|
+
const { transformed } = transformContent(input);
|
|
73
|
+
assert.equal(transformed, expected);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('transforms label-size="lg" to :label-size="400"', () => {
|
|
77
|
+
const input = '<dt-select-menu label-size="lg" />';
|
|
78
|
+
const expected = '<dt-select-menu :label-size="400" />';
|
|
79
|
+
const { transformed } = transformContent(input);
|
|
80
|
+
assert.equal(transformed, expected);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('speed prop transforms', () => {
|
|
85
|
+
it('transforms speed="sm" to :speed="200"', () => {
|
|
86
|
+
const input = '<dt-motion-text speed="sm" text="Hello" />';
|
|
87
|
+
const expected = '<dt-motion-text :speed="200" text="Hello" />';
|
|
88
|
+
const { transformed } = transformContent(input);
|
|
89
|
+
assert.equal(transformed, expected);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('transforms speed="lg" to :speed="400"', () => {
|
|
93
|
+
const input = '<dt-motion-text speed="lg" text="Hello" />';
|
|
94
|
+
const expected = '<dt-motion-text :speed="400" text="Hello" />';
|
|
95
|
+
const { transformed } = transformContent(input);
|
|
96
|
+
assert.equal(transformed, expected);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('Does NOT transform non-Dialtone components', () => {
|
|
101
|
+
it('ignores size on native elements', () => {
|
|
102
|
+
const input = '<div size="sm">Not a component</div>';
|
|
103
|
+
const { transformed, count } = transformContent(input);
|
|
104
|
+
assert.equal(transformed, input);
|
|
105
|
+
assert.equal(count, 0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('ignores size on non-dt components', () => {
|
|
109
|
+
const input = '<my-button size="sm">Click</my-button>';
|
|
110
|
+
const { transformed, count } = transformContent(input);
|
|
111
|
+
assert.equal(transformed, input);
|
|
112
|
+
assert.equal(count, 0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('ignores size on custom elements without dt- prefix', () => {
|
|
116
|
+
const input = '<app-select size="md" />';
|
|
117
|
+
const { transformed, count } = transformContent(input);
|
|
118
|
+
assert.equal(transformed, input);
|
|
119
|
+
assert.equal(count, 0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Does NOT transform already-numeric or dynamic values', () => {
|
|
124
|
+
it('ignores :size="200" (already numeric binding)', () => {
|
|
125
|
+
const input = '<dt-button :size="200">Click</dt-button>';
|
|
126
|
+
const { transformed, count } = transformContent(input);
|
|
127
|
+
assert.equal(transformed, input);
|
|
128
|
+
assert.equal(count, 0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('ignores :size="sm" (already a v-bind, consumer chose string)', () => {
|
|
132
|
+
const input = '<dt-button :size="sm">Click</dt-button>';
|
|
133
|
+
const { transformed, count } = transformContent(input);
|
|
134
|
+
assert.equal(transformed, input);
|
|
135
|
+
assert.equal(count, 0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('ignores :label-size="xs" (already a v-bind)', () => {
|
|
139
|
+
const input = '<dt-input :label-size="xs" />';
|
|
140
|
+
const { transformed, count } = transformContent(input);
|
|
141
|
+
assert.equal(transformed, input);
|
|
142
|
+
assert.equal(count, 0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('ignores :size="computedSize" (dynamic binding)', () => {
|
|
146
|
+
const input = '<dt-button :size="computedSize">Click</dt-button>';
|
|
147
|
+
const { transformed, count } = transformContent(input);
|
|
148
|
+
assert.equal(transformed, input);
|
|
149
|
+
assert.equal(count, 0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('ignores :size="iconSize" (icon size binding)', () => {
|
|
153
|
+
const input = '<dt-icon :size="iconSize" />';
|
|
154
|
+
const { transformed, count } = transformContent(input);
|
|
155
|
+
assert.equal(transformed, input);
|
|
156
|
+
assert.equal(count, 0);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('ignores numeric string size="200" on dt-icon (icon scale)', () => {
|
|
160
|
+
const input = '<dt-icon size="200" name="phone" />';
|
|
161
|
+
const { transformed, count } = transformContent(input);
|
|
162
|
+
assert.equal(transformed, input);
|
|
163
|
+
assert.equal(count, 0);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('PascalCase component names', () => {
|
|
168
|
+
it('transforms DtButton size', () => {
|
|
169
|
+
const input = '<DtButton size="sm">Click</DtButton>';
|
|
170
|
+
const expected = '<DtButton :size="200">Click</DtButton>';
|
|
171
|
+
const { transformed } = transformContent(input);
|
|
172
|
+
assert.equal(transformed, expected);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('transforms DtText size', () => {
|
|
176
|
+
const input = '<DtText size="xl">Title</DtText>';
|
|
177
|
+
const expected = '<DtText :size="500">Title</DtText>';
|
|
178
|
+
const { transformed } = transformContent(input);
|
|
179
|
+
assert.equal(transformed, expected);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('Multiple transforms in one tag', () => {
|
|
184
|
+
it('transforms size and label-size on same component', () => {
|
|
185
|
+
const input = '<dt-input size="lg" label-size="sm" label="Name" />';
|
|
186
|
+
const expected = '<dt-input :size="400" :label-size="200" label="Name" />';
|
|
187
|
+
const { transformed, count } = transformContent(input);
|
|
188
|
+
assert.equal(transformed, expected);
|
|
189
|
+
assert.equal(count, 2);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('Multiple components in one template', () => {
|
|
194
|
+
it('transforms all Dialtone components, ignores others', () => {
|
|
195
|
+
const input = `<template>
|
|
196
|
+
<div>
|
|
197
|
+
<dt-button size="sm">Small</dt-button>
|
|
198
|
+
<my-button size="sm">Not Dialtone</my-button>
|
|
199
|
+
<dt-text size="xl">Title</dt-text>
|
|
200
|
+
<dt-icon size="200" name="phone" />
|
|
201
|
+
</div>
|
|
202
|
+
</template>`;
|
|
203
|
+
const expected = `<template>
|
|
204
|
+
<div>
|
|
205
|
+
<dt-button :size="200">Small</dt-button>
|
|
206
|
+
<my-button size="sm">Not Dialtone</my-button>
|
|
207
|
+
<dt-text :size="500">Title</dt-text>
|
|
208
|
+
<dt-icon size="200" name="phone" />
|
|
209
|
+
</div>
|
|
210
|
+
</template>`;
|
|
211
|
+
const { transformed, count } = transformContent(input);
|
|
212
|
+
assert.equal(transformed, expected);
|
|
213
|
+
assert.equal(count, 2);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('Real-world patterns from the monorepo', () => {
|
|
218
|
+
it('transforms button in notice action slot', () => {
|
|
219
|
+
const input = '<dt-button size="sm" importance="outlined" kind="muted">Action</dt-button>';
|
|
220
|
+
const expected = '<dt-button :size="200" importance="outlined" kind="muted">Action</dt-button>';
|
|
221
|
+
const { transformed } = transformContent(input);
|
|
222
|
+
assert.equal(transformed, expected);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('transforms text inside card variant template', () => {
|
|
226
|
+
const input = '<dt-text as="p" kind="headline" size="md">Lorem ipsum</dt-text>';
|
|
227
|
+
const expected = '<dt-text as="p" kind="headline" :size="300">Lorem ipsum</dt-text>';
|
|
228
|
+
const { transformed } = transformContent(input);
|
|
229
|
+
assert.equal(transformed, expected);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('transforms avatar size', () => {
|
|
233
|
+
const input = '<dt-avatar size="md" :full-name="item.id" />';
|
|
234
|
+
const expected = '<dt-avatar :size="300" :full-name="item.id" />';
|
|
235
|
+
const { transformed } = transformContent(input);
|
|
236
|
+
assert.equal(transformed, expected);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('transforms motion-text speed in story', () => {
|
|
240
|
+
const input = '<dt-motion-text speed="md" text="Animated" :auto-start="false" />';
|
|
241
|
+
const expected = '<dt-motion-text :speed="300" text="Animated" :auto-start="false" />';
|
|
242
|
+
const { transformed } = transformContent(input);
|
|
243
|
+
assert.equal(transformed, expected);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('handles multiline component tags', () => {
|
|
247
|
+
const input = `<dt-button
|
|
248
|
+
size="xs"
|
|
249
|
+
kind="muted"
|
|
250
|
+
importance="clear"
|
|
251
|
+
>`;
|
|
252
|
+
const expected = `<dt-button
|
|
253
|
+
:size="100"
|
|
254
|
+
kind="muted"
|
|
255
|
+
importance="clear"
|
|
256
|
+
>`;
|
|
257
|
+
const { transformed } = transformContent(input);
|
|
258
|
+
assert.equal(transformed, expected);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('Future-proof: any compound *-size prop', () => {
|
|
263
|
+
it('transforms description-size="sm" to :description-size="200"', () => {
|
|
264
|
+
const input = '<dt-input description-size="sm" />';
|
|
265
|
+
const expected = '<dt-input :description-size="200" />';
|
|
266
|
+
const { transformed } = transformContent(input);
|
|
267
|
+
assert.equal(transformed, expected);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('transforms icon-size="lg" to :icon-size="400"', () => {
|
|
271
|
+
const input = '<dt-badge icon-size="lg" />';
|
|
272
|
+
const expected = '<dt-badge :icon-size="400" />';
|
|
273
|
+
const { transformed } = transformContent(input);
|
|
274
|
+
assert.equal(transformed, expected);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('transforms header-size="xl" to :header-size="500"', () => {
|
|
278
|
+
const input = '<dt-card header-size="xl" />';
|
|
279
|
+
const expected = '<dt-card :header-size="500" />';
|
|
280
|
+
const { transformed } = transformContent(input);
|
|
281
|
+
assert.equal(transformed, expected);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('camelCase prop names', () => {
|
|
286
|
+
it('transforms labelSize="xs" to :labelSize="100"', () => {
|
|
287
|
+
const input = '<dt-input labelSize="xs" />';
|
|
288
|
+
const expected = '<dt-input :labelSize="100" />';
|
|
289
|
+
const { transformed } = transformContent(input);
|
|
290
|
+
assert.equal(transformed, expected);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('transforms descriptionSize="md" to :descriptionSize="300"', () => {
|
|
294
|
+
const input = '<dt-input descriptionSize="md" />';
|
|
295
|
+
const expected = '<dt-input :descriptionSize="300" />';
|
|
296
|
+
const { transformed } = transformContent(input);
|
|
297
|
+
assert.equal(transformed, expected);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('Excluded props (not component scale sizes)', () => {
|
|
302
|
+
it('ignores button-width-size="md"', () => {
|
|
303
|
+
const input = '<dt-recipe-callbar-button button-width-size="md" />';
|
|
304
|
+
const { transformed, count } = transformContent(input);
|
|
305
|
+
assert.equal(transformed, input);
|
|
306
|
+
assert.equal(count, 0);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('ignores buttonWidthSize="lg"', () => {
|
|
310
|
+
const input = '<DtCallbarButton buttonWidthSize="lg" />';
|
|
311
|
+
const { transformed, count } = transformContent(input);
|
|
312
|
+
assert.equal(transformed, input);
|
|
313
|
+
assert.equal(count, 0);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('Edge cases', () => {
|
|
318
|
+
it('does not transform size inside text content', () => {
|
|
319
|
+
const input = '<dt-text>The size="sm" option is deprecated</dt-text>';
|
|
320
|
+
const { transformed, count } = transformContent(input);
|
|
321
|
+
assert.equal(transformed, input);
|
|
322
|
+
assert.equal(count, 0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('returns count of 0 for content with no matches', () => {
|
|
326
|
+
const input = '<div><span>No Dialtone here</span></div>';
|
|
327
|
+
const { count } = transformContent(input);
|
|
328
|
+
assert.equal(count, 0);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('handles empty string', () => {
|
|
332
|
+
const { transformed, count } = transformContent('');
|
|
333
|
+
assert.equal(transformed, '');
|
|
334
|
+
assert.equal(count, 0);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Migration: deprecated physical direction slot/prop/event names → logical equivalents.
|
|
2
|
+
// Covers Vue template directives only. Does NOT cover:
|
|
3
|
+
// - #icon on dt-button (ambiguous — requires manual migration)
|
|
4
|
+
// - Dynamic bindings or script-section references
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
description:
|
|
8
|
+
'Renames deprecated physical direction names (left/right/top/bottom/alpha/omega) to\n' +
|
|
9
|
+
'logical equivalents (start/end/blockStart/blockEnd) in Vue template slots, props,\n' +
|
|
10
|
+
'prop values, and events. Does NOT rename #icon on dt-button (ambiguous).',
|
|
11
|
+
patterns: ['**/*.{vue,html,md,js,ts,jsx,tsx}'],
|
|
12
|
+
expressions: [
|
|
13
|
+
// ── Slot renames ──────────────────────────────────────────────────────
|
|
14
|
+
// Longer patterns first to prevent partial matches.
|
|
15
|
+
// e.g. #leftIcon before #left, #rightIcon before #right
|
|
16
|
+
|
|
17
|
+
// #leftIcon → #startIcon
|
|
18
|
+
{ from: /#leftIcon/g, to: '#startIcon' },
|
|
19
|
+
// #rightIcon → #endIcon
|
|
20
|
+
{ from: /#rightIcon/g, to: '#endIcon' },
|
|
21
|
+
// #alphaIcon → #startIcon
|
|
22
|
+
{ from: /#alphaIcon/g, to: '#startIcon' },
|
|
23
|
+
// #omegaIcon → #endIcon
|
|
24
|
+
{ from: /#omegaIcon/g, to: '#endIcon' },
|
|
25
|
+
// #leftContent → #startContent
|
|
26
|
+
{ from: /#leftContent/g, to: '#startContent' },
|
|
27
|
+
// #rightContent → #endContent
|
|
28
|
+
{ from: /#rightContent/g, to: '#endContent' },
|
|
29
|
+
// #omega → #end (word boundary to avoid matching #omegaIcon)
|
|
30
|
+
// Scoped: only dt-split-button uses #omega
|
|
31
|
+
{ from: /#omega(?=[\s"'>])/g, to: '#end' },
|
|
32
|
+
|
|
33
|
+
// Generic short slot names (#left, #right, #bottom) are scoped to known
|
|
34
|
+
// Dialtone components to avoid renaming slots on non-Dialtone components.
|
|
35
|
+
// Uses multiline matching to find the slot within a dt-* or dt-recipe-* tag.
|
|
36
|
+
// Components: dt-item-layout, dt-recipe-callbox, dt-recipe-contact-centers-row,
|
|
37
|
+
// dt-recipe-general-row, dt-recipe-top-banner-info
|
|
38
|
+
|
|
39
|
+
// #left → #start (only on dt-item-layout, dt-recipe-general-row, dt-recipe-top-banner-info)
|
|
40
|
+
{ from: /(<dt-(?:item-layout|recipe-general-row|recipe-top-banner-info)[\s\S]*?)#left(?=[\s"'>])/gm, to: '$1#start' },
|
|
41
|
+
// #right → #end (only on dt-item-layout, dt-recipe-callbox, dt-recipe-contact-centers-row, dt-recipe-top-banner-info)
|
|
42
|
+
{ from: /(<dt-(?:item-layout|recipe-callbox|recipe-contact-centers-row|recipe-top-banner-info)[\s\S]*?)#right(?=[\s"'>])/gm, to: '$1#end' },
|
|
43
|
+
// #bottom → #blockEnd (only on dt-item-layout, dt-recipe-callbox)
|
|
44
|
+
{ from: /(<dt-(?:item-layout|recipe-callbox)[\s\S]*?)#bottom(?=[\s"'>])/gm, to: '$1#blockEnd' },
|
|
45
|
+
|
|
46
|
+
// ── Prop renames ──────────────────────────────────────────────────────
|
|
47
|
+
// Longer patterns first within each prefix group.
|
|
48
|
+
|
|
49
|
+
// alpha-* → start-* (longest first)
|
|
50
|
+
{ from: /alpha-trailing-class/g, to: 'start-trailing-class' },
|
|
51
|
+
{ from: /alpha-tooltip-text/g, to: 'start-tooltip-text' },
|
|
52
|
+
{ from: /alpha-leading-class/g, to: 'start-leading-class' },
|
|
53
|
+
{ from: /alpha-icon-position/g, to: 'start-icon-position' },
|
|
54
|
+
{ from: /alpha-aria-label/g, to: 'start-aria-label' },
|
|
55
|
+
{ from: /alpha-label-class/g, to: 'start-label-class' },
|
|
56
|
+
{ from: /alpha-disabled/g, to: 'start-disabled' },
|
|
57
|
+
{ from: /alpha-loading/g, to: 'start-loading' },
|
|
58
|
+
{ from: /alpha-active/g, to: 'start-active' },
|
|
59
|
+
|
|
60
|
+
// omega-* → end-* (longest first)
|
|
61
|
+
{ from: /omega-tooltip-text/g, to: 'end-tooltip-text' },
|
|
62
|
+
{ from: /omega-aria-label/g, to: 'end-aria-label' },
|
|
63
|
+
{ from: /omega-disabled/g, to: 'end-disabled' },
|
|
64
|
+
{ from: /omega-active/g, to: 'end-active' },
|
|
65
|
+
{ from: /omega-id/g, to: 'end-id' },
|
|
66
|
+
|
|
67
|
+
// layout class props
|
|
68
|
+
{ from: /bottom-class=/g, to: 'block-end-class=' },
|
|
69
|
+
{ from: /left-class=/g, to: 'start-class=' },
|
|
70
|
+
{ from: /right-class=/g, to: 'end-class=' },
|
|
71
|
+
|
|
72
|
+
// ── Prop value renames ────────────────────────────────────────────────
|
|
73
|
+
// icon-position values
|
|
74
|
+
{ from: /icon-position="left"/g, to: 'icon-position="start"' },
|
|
75
|
+
{ from: /icon-position="right"/g, to: 'icon-position="end"' },
|
|
76
|
+
{ from: /icon-position="top"/g, to: 'icon-position="blockStart"' },
|
|
77
|
+
{ from: /icon-position="bottom"/g, to: 'icon-position="blockEnd"' },
|
|
78
|
+
// sidebar-position values
|
|
79
|
+
{ from: /sidebar-position="left"/g, to: 'sidebar-position="start"' },
|
|
80
|
+
{ from: /sidebar-position="right"/g, to: 'sidebar-position="end"' },
|
|
81
|
+
|
|
82
|
+
// ── Event renames ─────────────────────────────────────────────────────
|
|
83
|
+
{ from: /@alpha-clicked/g, to: '@start-clicked' },
|
|
84
|
+
{ from: /@omega-clicked/g, to: '@end-clicked' },
|
|
85
|
+
],
|
|
86
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Migration script to convert t-shirt size props to numeric scale on Dialtone components.
|
|
5
|
+
*
|
|
6
|
+
* Transforms: size="sm" → :size="200", label-size="xs" → :label-size="100", speed="md" → :speed="300"
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx dialtone-migrate-tshirt-to-numeric [options]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* --cwd <path> Working directory (default: current directory)
|
|
13
|
+
* --dry-run Show changes without applying them
|
|
14
|
+
* --yes Apply all changes without prompting
|
|
15
|
+
* --help Show help
|
|
16
|
+
*
|
|
17
|
+
* Examples:
|
|
18
|
+
* npx dialtone-migrate-tshirt-to-numeric
|
|
19
|
+
* npx dialtone-migrate-tshirt-to-numeric --dry-run
|
|
20
|
+
* npx dialtone-migrate-tshirt-to-numeric --cwd ./src
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from 'fs/promises';
|
|
24
|
+
import { realpathSync } from 'node:fs';
|
|
25
|
+
import path from 'path';
|
|
26
|
+
import readline from 'readline';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Mapping
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
const SIZE_MAP = {
|
|
34
|
+
xs: '100',
|
|
35
|
+
sm: '200',
|
|
36
|
+
md: '300',
|
|
37
|
+
lg: '400',
|
|
38
|
+
xl: '500',
|
|
39
|
+
'2xl': '600',
|
|
40
|
+
'3xl': '700',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const TSHIRT_VALUES = Object.keys(SIZE_MAP).join('|');
|
|
44
|
+
|
|
45
|
+
// Match any prop ending in size/Size/speed/Speed with a t-shirt value.
|
|
46
|
+
// In the replacer, check that the character before the match is NOT a colon (v-bind).
|
|
47
|
+
const PROP_REGEX = new RegExp(
|
|
48
|
+
`([\\w-]*(?:[Ss]ize|[Ss]peed))="(${TSHIRT_VALUES})"`,
|
|
49
|
+
'g',
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Props that end in "size" but are NOT component scale sizes — exclude from migration
|
|
53
|
+
const EXCLUDED_PROPS = ['button-width-size', 'buttonWidthSize', 'background-size', 'backgroundSize', 'font-size', 'fontSize'];
|
|
54
|
+
|
|
55
|
+
// Only match on Dialtone component tags
|
|
56
|
+
// Use [\s\S] instead of [^>] to match across newlines in multiline tags
|
|
57
|
+
const DT_TAG_PATTERN = /<(dt-[\w-]+|Dt\w+)\b[\s\S]*?>/g;
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// File finder
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
async function findFiles (dir, extensions, ignore = []) {
|
|
64
|
+
const results = [];
|
|
65
|
+
|
|
66
|
+
async function walk (currentDir) {
|
|
67
|
+
try {
|
|
68
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
71
|
+
if (ignore.some(ig => fullPath.includes(ig))) continue;
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
await walk(fullPath);
|
|
74
|
+
} else if (entry.isFile()) {
|
|
75
|
+
if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
76
|
+
results.push(fullPath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// Skip unreadable directories
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await walk(dir);
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Transform logic
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
function transformContent (content) {
|
|
94
|
+
let transformed = content;
|
|
95
|
+
let count = 0;
|
|
96
|
+
|
|
97
|
+
// Replace t-shirt sizes only within Dialtone component tags
|
|
98
|
+
transformed = transformed.replace(DT_TAG_PATTERN, (tag) => {
|
|
99
|
+
PROP_REGEX.lastIndex = 0;
|
|
100
|
+
return tag.replace(PROP_REGEX, (match, propName, tshirt, offset, fullTag) => {
|
|
101
|
+
// Skip if preceded by ':' (already a v-bind expression)
|
|
102
|
+
if (offset > 0 && fullTag[offset - 1] === ':') return match;
|
|
103
|
+
// Skip excluded prop names (not component scale sizes)
|
|
104
|
+
if (EXCLUDED_PROPS.includes(propName)) return match;
|
|
105
|
+
if (SIZE_MAP[tshirt]) {
|
|
106
|
+
count++;
|
|
107
|
+
return `:${propName}="${SIZE_MAP[tshirt]}"`;
|
|
108
|
+
}
|
|
109
|
+
return match;
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return { transformed, count };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { transformContent, SIZE_MAP };
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// CLI
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
function printHelp () {
|
|
123
|
+
console.log(`
|
|
124
|
+
Usage: npx dialtone-migrate-tshirt-to-numeric [options]
|
|
125
|
+
|
|
126
|
+
Converts t-shirt size props to numeric scale on Dialtone components.
|
|
127
|
+
|
|
128
|
+
size="sm" → :size="200"
|
|
129
|
+
label-size="xs" → :label-size="100"
|
|
130
|
+
speed="md" → :speed="300"
|
|
131
|
+
|
|
132
|
+
Options:
|
|
133
|
+
--cwd <path> Working directory (default: current directory)
|
|
134
|
+
--dry-run Show changes without applying them
|
|
135
|
+
--yes Apply all changes without prompting
|
|
136
|
+
--help Show help
|
|
137
|
+
|
|
138
|
+
Size mapping:
|
|
139
|
+
xs → 100 sm → 200 md → 300 lg → 400 xl → 500
|
|
140
|
+
2xl → 600 3xl → 700
|
|
141
|
+
`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function prompt (question) {
|
|
145
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
146
|
+
return new Promise(resolve => {
|
|
147
|
+
rl.question(question, answer => {
|
|
148
|
+
rl.close();
|
|
149
|
+
resolve(answer.trim().toLowerCase());
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// eslint-disable-next-line complexity
|
|
155
|
+
async function main () {
|
|
156
|
+
const args = process.argv.slice(2);
|
|
157
|
+
|
|
158
|
+
if (args.includes('--help')) {
|
|
159
|
+
printHelp();
|
|
160
|
+
process.exit(0);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const dryRun = args.includes('--dry-run');
|
|
164
|
+
const autoYes = args.includes('--yes');
|
|
165
|
+
const cwdIndex = args.indexOf('--cwd');
|
|
166
|
+
const cwd = cwdIndex !== -1 && args[cwdIndex + 1]
|
|
167
|
+
? path.resolve(args[cwdIndex + 1])
|
|
168
|
+
: process.cwd();
|
|
169
|
+
|
|
170
|
+
console.log(`\nScanning ${cwd} for t-shirt size usage on Dialtone components...\n`);
|
|
171
|
+
|
|
172
|
+
const extensions = ['.vue', '.md', '.html', '.js', '.ts', '.jsx', '.tsx'];
|
|
173
|
+
const ignore = ['node_modules', 'dist', '.git', '.vuepress/public'];
|
|
174
|
+
const files = await findFiles(cwd, extensions, ignore);
|
|
175
|
+
|
|
176
|
+
const changes = [];
|
|
177
|
+
|
|
178
|
+
for (const file of files) {
|
|
179
|
+
const content = await fs.readFile(file, 'utf8');
|
|
180
|
+
const { transformed, count } = transformContent(content);
|
|
181
|
+
if (count > 0) {
|
|
182
|
+
changes.push({ file, content, transformed, count });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (changes.length === 0) {
|
|
187
|
+
console.log('No t-shirt size usage found. Nothing to migrate.');
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(`Found ${changes.reduce((sum, c) => sum + c.count, 0)} t-shirt size references across ${changes.length} files:\n`);
|
|
192
|
+
|
|
193
|
+
for (const { file, count } of changes) {
|
|
194
|
+
const rel = path.relative(cwd, file);
|
|
195
|
+
console.log(` ${rel} (${count} change${count > 1 ? 's' : ''})`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (dryRun) {
|
|
199
|
+
console.log('\n--dry-run: No files were modified.\n');
|
|
200
|
+
process.exit(0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!autoYes) {
|
|
204
|
+
const answer = await prompt('\nApply changes? (y/N) ');
|
|
205
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
206
|
+
console.log('Cancelled.');
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const { file, transformed } of changes) {
|
|
212
|
+
await fs.writeFile(file, transformed, 'utf8');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.log(`\nMigrated ${changes.reduce((sum, c) => sum + c.count, 0)} references across ${changes.length} files.\n`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Only run CLI when executed directly (not when imported for testing).
|
|
219
|
+
// Uses realpathSync to resolve symlinks from npx/npm bin shims.
|
|
220
|
+
const isDirectRun = (() => {
|
|
221
|
+
try {
|
|
222
|
+
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
})();
|
|
227
|
+
|
|
228
|
+
if (isDirectRun) {
|
|
229
|
+
main().catch(err => {
|
|
230
|
+
console.error(err);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Tests for dialtone-migrate-tshirt-to-numeric codemod.
|
|
5
|
+
* Run: node packages/dialtone-css/lib/build/js/dialtone_migrate_tshirt_to_numeric/test.mjs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { describe, it } from 'node:test';
|
|
10
|
+
import { transformContent } from './index.mjs';
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Tests
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
describe('Basic size prop transforms', () => {
|
|
17
|
+
it('transforms size="xs" to :size="100"', () => {
|
|
18
|
+
const input = '<dt-button size="xs">Click</dt-button>';
|
|
19
|
+
const expected = '<dt-button :size="100">Click</dt-button>';
|
|
20
|
+
const { transformed, count } = transformContent(input);
|
|
21
|
+
assert.equal(transformed, expected);
|
|
22
|
+
assert.equal(count, 1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('transforms size="sm" to :size="200"', () => {
|
|
26
|
+
const input = '<dt-text size="sm">Hello</dt-text>';
|
|
27
|
+
const expected = '<dt-text :size="200">Hello</dt-text>';
|
|
28
|
+
const { transformed } = transformContent(input);
|
|
29
|
+
assert.equal(transformed, expected);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('transforms size="md" to :size="300"', () => {
|
|
33
|
+
const input = '<dt-input size="md" label="Name" />';
|
|
34
|
+
const expected = '<dt-input :size="300" label="Name" />';
|
|
35
|
+
const { transformed } = transformContent(input);
|
|
36
|
+
assert.equal(transformed, expected);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('transforms size="lg" to :size="400"', () => {
|
|
40
|
+
const input = '<dt-toggle size="lg" />';
|
|
41
|
+
const expected = '<dt-toggle :size="400" />';
|
|
42
|
+
const { transformed } = transformContent(input);
|
|
43
|
+
assert.equal(transformed, expected);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('transforms size="xl" to :size="500"', () => {
|
|
47
|
+
const input = '<dt-segmented-control size="xl" />';
|
|
48
|
+
const expected = '<dt-segmented-control :size="500" />';
|
|
49
|
+
const { transformed } = transformContent(input);
|
|
50
|
+
assert.equal(transformed, expected);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('transforms size="2xl" to :size="600"', () => {
|
|
54
|
+
const input = '<dt-text kind="headline" size="2xl">Title</dt-text>';
|
|
55
|
+
const expected = '<dt-text kind="headline" :size="600">Title</dt-text>';
|
|
56
|
+
const { transformed } = transformContent(input);
|
|
57
|
+
assert.equal(transformed, expected);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('transforms size="3xl" to :size="700"', () => {
|
|
61
|
+
const input = '<dt-text kind="headline" size="3xl">Title</dt-text>';
|
|
62
|
+
const expected = '<dt-text kind="headline" :size="700">Title</dt-text>';
|
|
63
|
+
const { transformed } = transformContent(input);
|
|
64
|
+
assert.equal(transformed, expected);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('label-size prop transforms', () => {
|
|
69
|
+
it('transforms label-size="xs" to :label-size="100"', () => {
|
|
70
|
+
const input = '<dt-input label-size="xs" label="Name" />';
|
|
71
|
+
const expected = '<dt-input :label-size="100" label="Name" />';
|
|
72
|
+
const { transformed } = transformContent(input);
|
|
73
|
+
assert.equal(transformed, expected);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('transforms label-size="lg" to :label-size="400"', () => {
|
|
77
|
+
const input = '<dt-select-menu label-size="lg" />';
|
|
78
|
+
const expected = '<dt-select-menu :label-size="400" />';
|
|
79
|
+
const { transformed } = transformContent(input);
|
|
80
|
+
assert.equal(transformed, expected);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('speed prop transforms', () => {
|
|
85
|
+
it('transforms speed="sm" to :speed="200"', () => {
|
|
86
|
+
const input = '<dt-motion-text speed="sm" text="Hello" />';
|
|
87
|
+
const expected = '<dt-motion-text :speed="200" text="Hello" />';
|
|
88
|
+
const { transformed } = transformContent(input);
|
|
89
|
+
assert.equal(transformed, expected);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('transforms speed="lg" to :speed="400"', () => {
|
|
93
|
+
const input = '<dt-motion-text speed="lg" text="Hello" />';
|
|
94
|
+
const expected = '<dt-motion-text :speed="400" text="Hello" />';
|
|
95
|
+
const { transformed } = transformContent(input);
|
|
96
|
+
assert.equal(transformed, expected);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('Does NOT transform non-Dialtone components', () => {
|
|
101
|
+
it('ignores size on native elements', () => {
|
|
102
|
+
const input = '<div size="sm">Not a component</div>';
|
|
103
|
+
const { transformed, count } = transformContent(input);
|
|
104
|
+
assert.equal(transformed, input);
|
|
105
|
+
assert.equal(count, 0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('ignores size on non-dt components', () => {
|
|
109
|
+
const input = '<my-button size="sm">Click</my-button>';
|
|
110
|
+
const { transformed, count } = transformContent(input);
|
|
111
|
+
assert.equal(transformed, input);
|
|
112
|
+
assert.equal(count, 0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('ignores size on custom elements without dt- prefix', () => {
|
|
116
|
+
const input = '<app-select size="md" />';
|
|
117
|
+
const { transformed, count } = transformContent(input);
|
|
118
|
+
assert.equal(transformed, input);
|
|
119
|
+
assert.equal(count, 0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Does NOT transform already-numeric or dynamic values', () => {
|
|
124
|
+
it('ignores :size="200" (already numeric binding)', () => {
|
|
125
|
+
const input = '<dt-button :size="200">Click</dt-button>';
|
|
126
|
+
const { transformed, count } = transformContent(input);
|
|
127
|
+
assert.equal(transformed, input);
|
|
128
|
+
assert.equal(count, 0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('ignores :size="sm" (already a v-bind, consumer chose string)', () => {
|
|
132
|
+
const input = '<dt-button :size="sm">Click</dt-button>';
|
|
133
|
+
const { transformed, count } = transformContent(input);
|
|
134
|
+
assert.equal(transformed, input);
|
|
135
|
+
assert.equal(count, 0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('ignores :label-size="xs" (already a v-bind)', () => {
|
|
139
|
+
const input = '<dt-input :label-size="xs" />';
|
|
140
|
+
const { transformed, count } = transformContent(input);
|
|
141
|
+
assert.equal(transformed, input);
|
|
142
|
+
assert.equal(count, 0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('ignores :size="computedSize" (dynamic binding)', () => {
|
|
146
|
+
const input = '<dt-button :size="computedSize">Click</dt-button>';
|
|
147
|
+
const { transformed, count } = transformContent(input);
|
|
148
|
+
assert.equal(transformed, input);
|
|
149
|
+
assert.equal(count, 0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('ignores :size="iconSize" (icon size binding)', () => {
|
|
153
|
+
const input = '<dt-icon :size="iconSize" />';
|
|
154
|
+
const { transformed, count } = transformContent(input);
|
|
155
|
+
assert.equal(transformed, input);
|
|
156
|
+
assert.equal(count, 0);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('ignores numeric string size="200" on dt-icon (icon scale)', () => {
|
|
160
|
+
const input = '<dt-icon size="200" name="phone" />';
|
|
161
|
+
const { transformed, count } = transformContent(input);
|
|
162
|
+
assert.equal(transformed, input);
|
|
163
|
+
assert.equal(count, 0);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('PascalCase component names', () => {
|
|
168
|
+
it('transforms DtButton size', () => {
|
|
169
|
+
const input = '<DtButton size="sm">Click</DtButton>';
|
|
170
|
+
const expected = '<DtButton :size="200">Click</DtButton>';
|
|
171
|
+
const { transformed } = transformContent(input);
|
|
172
|
+
assert.equal(transformed, expected);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('transforms DtText size', () => {
|
|
176
|
+
const input = '<DtText size="xl">Title</DtText>';
|
|
177
|
+
const expected = '<DtText :size="500">Title</DtText>';
|
|
178
|
+
const { transformed } = transformContent(input);
|
|
179
|
+
assert.equal(transformed, expected);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('Multiple transforms in one tag', () => {
|
|
184
|
+
it('transforms size and label-size on same component', () => {
|
|
185
|
+
const input = '<dt-input size="lg" label-size="sm" label="Name" />';
|
|
186
|
+
const expected = '<dt-input :size="400" :label-size="200" label="Name" />';
|
|
187
|
+
const { transformed, count } = transformContent(input);
|
|
188
|
+
assert.equal(transformed, expected);
|
|
189
|
+
assert.equal(count, 2);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('Multiple components in one template', () => {
|
|
194
|
+
it('transforms all Dialtone components, ignores others', () => {
|
|
195
|
+
const input = `<template>
|
|
196
|
+
<div>
|
|
197
|
+
<dt-button size="sm">Small</dt-button>
|
|
198
|
+
<my-button size="sm">Not Dialtone</my-button>
|
|
199
|
+
<dt-text size="xl">Title</dt-text>
|
|
200
|
+
<dt-icon size="200" name="phone" />
|
|
201
|
+
</div>
|
|
202
|
+
</template>`;
|
|
203
|
+
const expected = `<template>
|
|
204
|
+
<div>
|
|
205
|
+
<dt-button :size="200">Small</dt-button>
|
|
206
|
+
<my-button size="sm">Not Dialtone</my-button>
|
|
207
|
+
<dt-text :size="500">Title</dt-text>
|
|
208
|
+
<dt-icon size="200" name="phone" />
|
|
209
|
+
</div>
|
|
210
|
+
</template>`;
|
|
211
|
+
const { transformed, count } = transformContent(input);
|
|
212
|
+
assert.equal(transformed, expected);
|
|
213
|
+
assert.equal(count, 2);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('Real-world patterns from the monorepo', () => {
|
|
218
|
+
it('transforms button in notice action slot', () => {
|
|
219
|
+
const input = '<dt-button size="sm" importance="outlined" kind="muted">Action</dt-button>';
|
|
220
|
+
const expected = '<dt-button :size="200" importance="outlined" kind="muted">Action</dt-button>';
|
|
221
|
+
const { transformed } = transformContent(input);
|
|
222
|
+
assert.equal(transformed, expected);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('transforms text inside card variant template', () => {
|
|
226
|
+
const input = '<dt-text as="p" kind="headline" size="md">Lorem ipsum</dt-text>';
|
|
227
|
+
const expected = '<dt-text as="p" kind="headline" :size="300">Lorem ipsum</dt-text>';
|
|
228
|
+
const { transformed } = transformContent(input);
|
|
229
|
+
assert.equal(transformed, expected);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('transforms avatar size', () => {
|
|
233
|
+
const input = '<dt-avatar size="md" :full-name="item.id" />';
|
|
234
|
+
const expected = '<dt-avatar :size="300" :full-name="item.id" />';
|
|
235
|
+
const { transformed } = transformContent(input);
|
|
236
|
+
assert.equal(transformed, expected);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('transforms motion-text speed in story', () => {
|
|
240
|
+
const input = '<dt-motion-text speed="md" text="Animated" :auto-start="false" />';
|
|
241
|
+
const expected = '<dt-motion-text :speed="300" text="Animated" :auto-start="false" />';
|
|
242
|
+
const { transformed } = transformContent(input);
|
|
243
|
+
assert.equal(transformed, expected);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('handles multiline component tags', () => {
|
|
247
|
+
const input = `<dt-button
|
|
248
|
+
size="xs"
|
|
249
|
+
kind="muted"
|
|
250
|
+
importance="clear"
|
|
251
|
+
>`;
|
|
252
|
+
const expected = `<dt-button
|
|
253
|
+
:size="100"
|
|
254
|
+
kind="muted"
|
|
255
|
+
importance="clear"
|
|
256
|
+
>`;
|
|
257
|
+
const { transformed } = transformContent(input);
|
|
258
|
+
assert.equal(transformed, expected);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('Future-proof: any compound *-size prop', () => {
|
|
263
|
+
it('transforms description-size="sm" to :description-size="200"', () => {
|
|
264
|
+
const input = '<dt-input description-size="sm" />';
|
|
265
|
+
const expected = '<dt-input :description-size="200" />';
|
|
266
|
+
const { transformed } = transformContent(input);
|
|
267
|
+
assert.equal(transformed, expected);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('transforms icon-size="lg" to :icon-size="400"', () => {
|
|
271
|
+
const input = '<dt-badge icon-size="lg" />';
|
|
272
|
+
const expected = '<dt-badge :icon-size="400" />';
|
|
273
|
+
const { transformed } = transformContent(input);
|
|
274
|
+
assert.equal(transformed, expected);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('transforms header-size="xl" to :header-size="500"', () => {
|
|
278
|
+
const input = '<dt-card header-size="xl" />';
|
|
279
|
+
const expected = '<dt-card :header-size="500" />';
|
|
280
|
+
const { transformed } = transformContent(input);
|
|
281
|
+
assert.equal(transformed, expected);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('camelCase prop names', () => {
|
|
286
|
+
it('transforms labelSize="xs" to :labelSize="100"', () => {
|
|
287
|
+
const input = '<dt-input labelSize="xs" />';
|
|
288
|
+
const expected = '<dt-input :labelSize="100" />';
|
|
289
|
+
const { transformed } = transformContent(input);
|
|
290
|
+
assert.equal(transformed, expected);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('transforms descriptionSize="md" to :descriptionSize="300"', () => {
|
|
294
|
+
const input = '<dt-input descriptionSize="md" />';
|
|
295
|
+
const expected = '<dt-input :descriptionSize="300" />';
|
|
296
|
+
const { transformed } = transformContent(input);
|
|
297
|
+
assert.equal(transformed, expected);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('Excluded props (not component scale sizes)', () => {
|
|
302
|
+
it('ignores button-width-size="md"', () => {
|
|
303
|
+
const input = '<dt-recipe-callbar-button button-width-size="md" />';
|
|
304
|
+
const { transformed, count } = transformContent(input);
|
|
305
|
+
assert.equal(transformed, input);
|
|
306
|
+
assert.equal(count, 0);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('ignores buttonWidthSize="lg"', () => {
|
|
310
|
+
const input = '<DtCallbarButton buttonWidthSize="lg" />';
|
|
311
|
+
const { transformed, count } = transformContent(input);
|
|
312
|
+
assert.equal(transformed, input);
|
|
313
|
+
assert.equal(count, 0);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('Edge cases', () => {
|
|
318
|
+
it('does not transform size inside text content', () => {
|
|
319
|
+
const input = '<dt-text>The size="sm" option is deprecated</dt-text>';
|
|
320
|
+
const { transformed, count } = transformContent(input);
|
|
321
|
+
assert.equal(transformed, input);
|
|
322
|
+
assert.equal(count, 0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('returns count of 0 for content with no matches', () => {
|
|
326
|
+
const input = '<div><span>No Dialtone here</span></div>';
|
|
327
|
+
const { count } = transformContent(input);
|
|
328
|
+
assert.equal(count, 0);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('handles empty string', () => {
|
|
332
|
+
const { transformed, count } = transformContent('');
|
|
333
|
+
assert.equal(transformed, '');
|
|
334
|
+
assert.equal(count, 0);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Migration: deprecated physical direction slot/prop/event names → logical equivalents.
|
|
2
|
+
// Covers Vue template directives only. Does NOT cover:
|
|
3
|
+
// - #icon on dt-button (ambiguous — requires manual migration)
|
|
4
|
+
// - Dynamic bindings or script-section references
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
description:
|
|
8
|
+
'Renames deprecated physical direction names (left/right/top/bottom/alpha/omega) to\n' +
|
|
9
|
+
'logical equivalents (start/end/blockStart/blockEnd) in Vue template slots, props,\n' +
|
|
10
|
+
'prop values, and events. Does NOT rename #icon on dt-button (ambiguous).',
|
|
11
|
+
patterns: ['**/*.{vue,html,md,js,ts,jsx,tsx}'],
|
|
12
|
+
expressions: [
|
|
13
|
+
// ── Slot renames ──────────────────────────────────────────────────────
|
|
14
|
+
// Longer patterns first to prevent partial matches.
|
|
15
|
+
// e.g. #leftIcon before #left, #rightIcon before #right
|
|
16
|
+
|
|
17
|
+
// #leftIcon → #startIcon
|
|
18
|
+
{ from: /#leftIcon/g, to: '#startIcon' },
|
|
19
|
+
// #rightIcon → #endIcon
|
|
20
|
+
{ from: /#rightIcon/g, to: '#endIcon' },
|
|
21
|
+
// #alphaIcon → #startIcon
|
|
22
|
+
{ from: /#alphaIcon/g, to: '#startIcon' },
|
|
23
|
+
// #omegaIcon → #endIcon
|
|
24
|
+
{ from: /#omegaIcon/g, to: '#endIcon' },
|
|
25
|
+
// #leftContent → #startContent
|
|
26
|
+
{ from: /#leftContent/g, to: '#startContent' },
|
|
27
|
+
// #rightContent → #endContent
|
|
28
|
+
{ from: /#rightContent/g, to: '#endContent' },
|
|
29
|
+
// #omega → #end (word boundary to avoid matching #omegaIcon)
|
|
30
|
+
// Scoped: only dt-split-button uses #omega
|
|
31
|
+
{ from: /#omega(?=[\s"'>])/g, to: '#end' },
|
|
32
|
+
|
|
33
|
+
// Generic short slot names (#left, #right, #bottom) are scoped to known
|
|
34
|
+
// Dialtone components to avoid renaming slots on non-Dialtone components.
|
|
35
|
+
// Uses multiline matching to find the slot within a dt-* or dt-recipe-* tag.
|
|
36
|
+
// Components: dt-item-layout, dt-recipe-callbox, dt-recipe-contact-centers-row,
|
|
37
|
+
// dt-recipe-general-row, dt-recipe-top-banner-info
|
|
38
|
+
|
|
39
|
+
// #left → #start (only on dt-item-layout, dt-recipe-general-row, dt-recipe-top-banner-info)
|
|
40
|
+
{ from: /(<dt-(?:item-layout|recipe-general-row|recipe-top-banner-info)[\s\S]*?)#left(?=[\s"'>])/gm, to: '$1#start' },
|
|
41
|
+
// #right → #end (only on dt-item-layout, dt-recipe-callbox, dt-recipe-contact-centers-row, dt-recipe-top-banner-info)
|
|
42
|
+
{ from: /(<dt-(?:item-layout|recipe-callbox|recipe-contact-centers-row|recipe-top-banner-info)[\s\S]*?)#right(?=[\s"'>])/gm, to: '$1#end' },
|
|
43
|
+
// #bottom → #blockEnd (only on dt-item-layout, dt-recipe-callbox)
|
|
44
|
+
{ from: /(<dt-(?:item-layout|recipe-callbox)[\s\S]*?)#bottom(?=[\s"'>])/gm, to: '$1#blockEnd' },
|
|
45
|
+
|
|
46
|
+
// ── Prop renames ──────────────────────────────────────────────────────
|
|
47
|
+
// Longer patterns first within each prefix group.
|
|
48
|
+
|
|
49
|
+
// alpha-* → start-* (longest first)
|
|
50
|
+
{ from: /alpha-trailing-class/g, to: 'start-trailing-class' },
|
|
51
|
+
{ from: /alpha-tooltip-text/g, to: 'start-tooltip-text' },
|
|
52
|
+
{ from: /alpha-leading-class/g, to: 'start-leading-class' },
|
|
53
|
+
{ from: /alpha-icon-position/g, to: 'start-icon-position' },
|
|
54
|
+
{ from: /alpha-aria-label/g, to: 'start-aria-label' },
|
|
55
|
+
{ from: /alpha-label-class/g, to: 'start-label-class' },
|
|
56
|
+
{ from: /alpha-disabled/g, to: 'start-disabled' },
|
|
57
|
+
{ from: /alpha-loading/g, to: 'start-loading' },
|
|
58
|
+
{ from: /alpha-active/g, to: 'start-active' },
|
|
59
|
+
|
|
60
|
+
// omega-* → end-* (longest first)
|
|
61
|
+
{ from: /omega-tooltip-text/g, to: 'end-tooltip-text' },
|
|
62
|
+
{ from: /omega-aria-label/g, to: 'end-aria-label' },
|
|
63
|
+
{ from: /omega-disabled/g, to: 'end-disabled' },
|
|
64
|
+
{ from: /omega-active/g, to: 'end-active' },
|
|
65
|
+
{ from: /omega-id/g, to: 'end-id' },
|
|
66
|
+
|
|
67
|
+
// layout class props
|
|
68
|
+
{ from: /bottom-class=/g, to: 'block-end-class=' },
|
|
69
|
+
{ from: /left-class=/g, to: 'start-class=' },
|
|
70
|
+
{ from: /right-class=/g, to: 'end-class=' },
|
|
71
|
+
|
|
72
|
+
// ── Prop value renames ────────────────────────────────────────────────
|
|
73
|
+
// icon-position values
|
|
74
|
+
{ from: /icon-position="left"/g, to: 'icon-position="start"' },
|
|
75
|
+
{ from: /icon-position="right"/g, to: 'icon-position="end"' },
|
|
76
|
+
{ from: /icon-position="top"/g, to: 'icon-position="blockStart"' },
|
|
77
|
+
{ from: /icon-position="bottom"/g, to: 'icon-position="blockEnd"' },
|
|
78
|
+
// sidebar-position values
|
|
79
|
+
{ from: /sidebar-position="left"/g, to: 'sidebar-position="start"' },
|
|
80
|
+
{ from: /sidebar-position="right"/g, to: 'sidebar-position="end"' },
|
|
81
|
+
|
|
82
|
+
// ── Event renames ─────────────────────────────────────────────────────
|
|
83
|
+
{ from: /@alpha-clicked/g, to: '@start-clicked' },
|
|
84
|
+
{ from: /@omega-clicked/g, to: '@end-clicked' },
|
|
85
|
+
],
|
|
86
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dialpad/dialtone-css",
|
|
3
|
-
"version": "8.78.0-next.
|
|
3
|
+
"version": "8.78.0-next.3",
|
|
4
4
|
"description": "Dialpad's design system",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Dialpad",
|
|
@@ -78,9 +78,9 @@
|
|
|
78
78
|
"less": "^4.2.0",
|
|
79
79
|
"oslllo-svg-fixer": "^2.2.0",
|
|
80
80
|
"through2": "^4.0.2",
|
|
81
|
-
"@dialpad/
|
|
82
|
-
"@dialpad/
|
|
83
|
-
"@dialpad/
|
|
81
|
+
"@dialpad/postcss-responsive-variations": "1.2.4-next.1",
|
|
82
|
+
"@dialpad/dialtone-tokens": "1.48.0-next.13",
|
|
83
|
+
"@dialpad/stylelint-plugin-dialtone": "1.4.0-next.2"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
86
|
"chalk": "^5.2.0",
|