@dialpad/dialtone-css 8.78.0-next.1 → 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.
Files changed (108) hide show
  1. package/README.md +0 -2
  2. package/lib/build/js/dialtone_migrate_flex_to_stack/examples-edge-cases.vue +26 -0
  3. package/lib/build/js/dialtone_migrate_flex_to_stack/index.mjs +56 -15
  4. package/lib/build/js/dialtone_migrate_tshirt_to_numeric/index.mjs +233 -0
  5. package/lib/build/js/dialtone_migrate_tshirt_to_numeric/test.mjs +336 -0
  6. package/lib/build/js/dialtone_migration_helper/configs/physical-to-logical.mjs +86 -0
  7. package/lib/build/js/dialtone_migration_helper/configs/size-to-layout.mjs +212 -0
  8. package/lib/build/js/dialtone_migration_helper/configs/space-to-spacing.mjs +48 -0
  9. package/lib/build/js/dialtone_migration_helper/configs/stack-gap-to-spacing.mjs +88 -0
  10. package/lib/build/js/dialtone_migration_helper/configs/utility-class-to-token-stops.mjs +135 -0
  11. package/lib/build/js/dialtone_migration_helper/tests/size-to-layout-test-examples.vue +275 -0
  12. package/lib/build/js/dialtone_migration_helper/tests/space-to-spacing-test-examples.vue +193 -0
  13. package/lib/build/less/components/avatar.less +8 -19
  14. package/lib/build/less/components/badge.less +22 -20
  15. package/lib/build/less/components/banner.less +5 -5
  16. package/lib/build/less/components/breadcrumbs.less +4 -4
  17. package/lib/build/less/components/button.less +39 -39
  18. package/lib/build/less/components/card.less +4 -4
  19. package/lib/build/less/components/chip.less +41 -51
  20. package/lib/build/less/components/codeblock.less +2 -2
  21. package/lib/build/less/components/collapsible.less +2 -2
  22. package/lib/build/less/components/combobox-multi-select.less +8 -8
  23. package/lib/build/less/components/combobox-with-popover.less +1 -1
  24. package/lib/build/less/components/combobox.less +5 -5
  25. package/lib/build/less/components/datepicker.less +6 -6
  26. package/lib/build/less/components/description-list.less +14 -3
  27. package/lib/build/less/components/dropdown.less +4 -4
  28. package/lib/build/less/components/emoji-picker.less +35 -35
  29. package/lib/build/less/components/empty-state.less +5 -5
  30. package/lib/build/less/components/filter-pill.less +5 -5
  31. package/lib/build/less/components/forms.less +10 -10
  32. package/lib/build/less/components/image-viewer.less +2 -2
  33. package/lib/build/less/components/input.less +17 -22
  34. package/lib/build/less/components/item-layout.less +8 -8
  35. package/lib/build/less/components/keyboard-shortcut.less +3 -3
  36. package/lib/build/less/components/link.less +5 -5
  37. package/lib/build/less/components/list-group.less +1 -1
  38. package/lib/build/less/components/list-item-group.less +1 -1
  39. package/lib/build/less/components/list-item.less +9 -9
  40. package/lib/build/less/components/modal.less +20 -20
  41. package/lib/build/less/components/notice.less +11 -11
  42. package/lib/build/less/components/pagination.less +5 -5
  43. package/lib/build/less/components/popover.less +5 -5
  44. package/lib/build/less/components/radio-checkbox.less +11 -10
  45. package/lib/build/less/components/rich-text-editor.less +13 -13
  46. package/lib/build/less/components/scrollbar.less +2 -2
  47. package/lib/build/less/components/segmented-control.less +6 -6
  48. package/lib/build/less/components/selects.less +18 -13
  49. package/lib/build/less/components/skeleton.less +4 -4
  50. package/lib/build/less/components/stack.less +24 -69
  51. package/lib/build/less/components/table.less +6 -7
  52. package/lib/build/less/components/tabs.less +24 -24
  53. package/lib/build/less/components/toast.less +16 -16
  54. package/lib/build/less/components/toggle.less +23 -23
  55. package/lib/build/less/components/tooltip.less +27 -27
  56. package/lib/build/less/dialtone-reset.less +3 -3
  57. package/lib/build/less/dialtone-transitions.less +4 -4
  58. package/lib/build/less/dialtone.less +2 -2
  59. package/lib/build/less/recipes/attachment_carousel.less +13 -13
  60. package/lib/build/less/recipes/callbar_button.less +1 -1
  61. package/lib/build/less/recipes/callbar_button_with_dropdown.less +7 -7
  62. package/lib/build/less/recipes/callbar_button_with_popover.less +8 -8
  63. package/lib/build/less/recipes/callbox.less +6 -6
  64. package/lib/build/less/recipes/contact_info.less +9 -9
  65. package/lib/build/less/recipes/editor.less +12 -12
  66. package/lib/build/less/recipes/emoji_row.less +8 -8
  67. package/lib/build/less/recipes/feed_item_pill.less +13 -13
  68. package/lib/build/less/recipes/feed_item_row.less +10 -10
  69. package/lib/build/less/recipes/grouped_chip.less +2 -2
  70. package/lib/build/less/recipes/ivr_node.less +13 -13
  71. package/lib/build/less/recipes/leftbar_row.less +23 -23
  72. package/lib/build/less/recipes/message_input.less +16 -16
  73. package/lib/build/less/recipes/settings_menu_button.less +10 -10
  74. package/lib/build/less/recipes/time_pill.less +1 -1
  75. package/lib/build/less/recipes/top_banner_info.less +8 -8
  76. package/lib/build/less/recipes/unread_pill.less +2 -2
  77. package/lib/build/less/themes/default.less +1 -1
  78. package/lib/build/less/utilities/backgrounds.less +3 -3
  79. package/lib/build/less/utilities/effects.less +20 -20
  80. package/lib/build/less/utilities/flex.less +11 -11
  81. package/lib/build/less/utilities/layout.less +4 -4
  82. package/lib/build/less/utilities/sizing.less +172 -0
  83. package/lib/build/less/utilities/spacing.less +49 -49
  84. package/lib/build/less/utilities/typography.less +2 -2
  85. package/lib/build/less/variables/sizes.less +8 -8
  86. package/lib/dist/dialtone-default-theme.css +5220 -1117
  87. package/lib/dist/dialtone-default-theme.min.css +1 -1
  88. package/lib/dist/dialtone-docs.json +1 -1
  89. package/lib/dist/dialtone.css +5203 -1117
  90. package/lib/dist/dialtone.min.css +1 -1
  91. package/lib/dist/js/dialtone_migrate_flex_to_stack/examples-edge-cases.vue +26 -0
  92. package/lib/dist/js/dialtone_migrate_flex_to_stack/index.mjs +56 -15
  93. package/lib/dist/js/dialtone_migrate_tshirt_to_numeric/index.mjs +233 -0
  94. package/lib/dist/js/dialtone_migrate_tshirt_to_numeric/test.mjs +336 -0
  95. package/lib/dist/js/dialtone_migration_helper/configs/physical-to-logical.mjs +86 -0
  96. package/lib/dist/js/dialtone_migration_helper/configs/size-to-layout.mjs +212 -0
  97. package/lib/dist/js/dialtone_migration_helper/configs/space-to-spacing.mjs +48 -0
  98. package/lib/dist/js/dialtone_migration_helper/configs/stack-gap-to-spacing.mjs +88 -0
  99. package/lib/dist/js/dialtone_migration_helper/configs/utility-class-to-token-stops.mjs +135 -0
  100. package/lib/dist/js/dialtone_migration_helper/tests/size-to-layout-test-examples.vue +275 -0
  101. package/lib/dist/js/dialtone_migration_helper/tests/space-to-spacing-test-examples.vue +193 -0
  102. package/lib/dist/tokens/tokens-base-dark.css +17 -0
  103. package/lib/dist/tokens/tokens-base-light.css +17 -0
  104. package/lib/dist/tokens/tokens-debug-base.css +17 -0
  105. package/lib/dist/tokens-docs.json +1 -1
  106. package/package.json +3 -3
  107. package/lib/build/js/dialtone_migration_helper/configs/space-to-size.mjs +0 -15
  108. package/lib/dist/js/dialtone_migration_helper/configs/space-to-size.mjs +0 -15
@@ -292,6 +292,32 @@
292
292
 
293
293
  <!-- TEST 47: Empty element with flex - Should convert -->
294
294
  <div class="d-d-flex d-fd-column"></div>
295
+
296
+ <!-- ============================================ -->
297
+ <!-- NEW TESTS: NEW-FORMAT GAP UTILITIES (d-g-*) -->
298
+ <!-- ============================================ -->
299
+
300
+ <!-- TEST 48: New-format gap d-g-100 (8px) - Should convert to gap="100" -->
301
+ <div class="d-d-flex d-ai-center d-g-100">
302
+ <span>New gap format 8px</span>
303
+ </div>
304
+
305
+ <!-- TEST 49: New-format gap d-g-200 (16px) - Should convert to gap="200" -->
306
+ <div class="d-d-flex d-fd-row d-g-200">
307
+ <span>Item 1</span>
308
+ <span>Item 2</span>
309
+ </div>
310
+
311
+ <!-- TEST 50: New-format gap d-g-400 (32px) with other props - Should convert -->
312
+ <div class="d-d-flex d-fd-column d-ai-center d-jc-between d-g-400">
313
+ <span>Full props with new gap</span>
314
+ <span>Example</span>
315
+ </div>
316
+
317
+ <!-- TEST 51: New-format gap d-g-50 (4px) - Should convert to gap="50" -->
318
+ <div class="d-d-flex d-ai-center d-g-50">
319
+ <span>Small gap 4px</span>
320
+ </div>
295
321
  </template>
296
322
 
297
323
  <script setup>
@@ -142,23 +142,64 @@ const FLEX_TO_PROP = {
142
142
  'd-fd-row-reverse': { prop: 'direction', value: 'row-reverse' },
143
143
  'd-fd-column-reverse': { prop: 'direction', value: 'column-reverse' },
144
144
 
145
- // Gap mappings (d-g* → gap prop)
145
+ // Gap mappings (d-g* → gap prop) — old pixel-based utilities
146
146
  'd-g0': { prop: 'gap', value: '0' },
147
- 'd-g8': { prop: 'gap', value: '400' },
148
- 'd-g16': { prop: 'gap', value: '500' },
149
- 'd-g24': { prop: 'gap', value: '550' },
150
- 'd-g32': { prop: 'gap', value: '600' },
151
- 'd-g48': { prop: 'gap', value: '650' },
152
- 'd-g64': { prop: 'gap', value: '700' },
153
-
154
- // Grid-gap mappings (d-gg* gap prop) - deprecated utilities, same as d-g*
147
+ 'd-g1': { prop: 'gap', value: '1' },
148
+ 'd-g2': { prop: 'gap', value: '25' },
149
+ 'd-g4': { prop: 'gap', value: '50' },
150
+ 'd-g6': { prop: 'gap', value: '75' },
151
+ 'd-g8': { prop: 'gap', value: '100' },
152
+ 'd-g10': { prop: 'gap', value: '125' },
153
+ 'd-g12': { prop: 'gap', value: '150' },
154
+ 'd-g14': { prop: 'gap', value: '175' },
155
+ 'd-g16': { prop: 'gap', value: '200' },
156
+ 'd-g20': { prop: 'gap', value: '250' },
157
+ 'd-g24': { prop: 'gap', value: '300' },
158
+ 'd-g32': { prop: 'gap', value: '400' },
159
+ 'd-g48': { prop: 'gap', value: '600' },
160
+ 'd-g64': { prop: 'gap', value: '800' },
161
+
162
+ // Gap mappings (d-g-* → gap prop) — new token-stop-based utilities
163
+ 'd-g-0': { prop: 'gap', value: '0' },
164
+ 'd-g-1': { prop: 'gap', value: '1' },
165
+ 'd-g-25': { prop: 'gap', value: '25' },
166
+ 'd-g-50': { prop: 'gap', value: '50' },
167
+ 'd-g-75': { prop: 'gap', value: '75' },
168
+ 'd-g-100': { prop: 'gap', value: '100' },
169
+ 'd-g-125': { prop: 'gap', value: '125' },
170
+ 'd-g-150': { prop: 'gap', value: '150' },
171
+ 'd-g-175': { prop: 'gap', value: '175' },
172
+ 'd-g-200': { prop: 'gap', value: '200' },
173
+ 'd-g-250': { prop: 'gap', value: '250' },
174
+ 'd-g-300': { prop: 'gap', value: '300' },
175
+ 'd-g-350': { prop: 'gap', value: '350' },
176
+ 'd-g-400': { prop: 'gap', value: '400' },
177
+ 'd-g-450': { prop: 'gap', value: '450' },
178
+ 'd-g-500': { prop: 'gap', value: '500' },
179
+ 'd-g-525': { prop: 'gap', value: '525' },
180
+ 'd-g-550': { prop: 'gap', value: '550' },
181
+ 'd-g-600': { prop: 'gap', value: '600' },
182
+ 'd-g-650': { prop: 'gap', value: '650' },
183
+ 'd-g-700': { prop: 'gap', value: '700' },
184
+ 'd-g-750': { prop: 'gap', value: '750' },
185
+ 'd-g-800': { prop: 'gap', value: '800' },
186
+
187
+ // Grid-gap mappings (d-gg* → gap prop) — deprecated utilities
155
188
  'd-gg0': { prop: 'gap', value: '0' },
156
- 'd-gg8': { prop: 'gap', value: '400' },
157
- 'd-gg16': { prop: 'gap', value: '500' },
158
- 'd-gg24': { prop: 'gap', value: '550' },
159
- 'd-gg32': { prop: 'gap', value: '600' },
160
- 'd-gg48': { prop: 'gap', value: '650' },
161
- 'd-gg64': { prop: 'gap', value: '700' },
189
+ 'd-gg1': { prop: 'gap', value: '1' },
190
+ 'd-gg2': { prop: 'gap', value: '25' },
191
+ 'd-gg4': { prop: 'gap', value: '50' },
192
+ 'd-gg6': { prop: 'gap', value: '75' },
193
+ 'd-gg8': { prop: 'gap', value: '100' },
194
+ 'd-gg10': { prop: 'gap', value: '125' },
195
+ 'd-gg12': { prop: 'gap', value: '150' },
196
+ 'd-gg14': { prop: 'gap', value: '175' },
197
+ 'd-gg16': { prop: 'gap', value: '200' },
198
+ 'd-gg20': { prop: 'gap', value: '250' },
199
+ 'd-gg24': { prop: 'gap', value: '300' },
200
+ 'd-gg32': { prop: 'gap', value: '400' },
201
+ 'd-gg48': { prop: 'gap', value: '600' },
202
+ 'd-gg64': { prop: 'gap', value: '800' },
162
203
  };
163
204
 
164
205
  // Classes to remove (redundant on dt-stack)
@@ -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
+ });