@dialpad/dialtone-css 8.81.0-next.5 → 8.81.0-next.6

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.
@@ -18,7 +18,11 @@
18
18
  * --yes Apply all changes without prompting
19
19
  * --health-check Report migration status without modifying files
20
20
  * --all Run all required migrations without selection prompt
21
+ * --include-opt-in Also run opt-in migrations (with --all, or on its own = everything)
21
22
  * --only <ids> Comma-separated list of migration IDs to run
23
+ * --package <name> Package name to use for injected component imports
24
+ * (default: @dialpad/dialtone-vue). Useful when running
25
+ * Dialtone under a custom package alias (e.g. @dialpad/dialtone-next).
22
26
  * --help Show help
23
27
  *
24
28
  * Examples:
@@ -50,6 +54,7 @@ const __dirname = path.dirname(__filename);
50
54
  * @property {'config'|'standalone'|'manual'} type
51
55
  * @property {string} [configName] - Config filename for migration-helper type
52
56
  * @property {string} [scriptDir] - Directory name for standalone scripts
57
+ * @property {boolean} [supportsPackageArg] - Forward --package to this standalone script
53
58
  * @property {string[]} [extraArgs] - Extra CLI args to forward
54
59
  * @property {RegExp[]} detectPatterns - Patterns that indicate migration is still needed
55
60
  * @property {string[]} fileExtensions - File extensions to scan during health check
@@ -250,6 +255,8 @@ const MIGRATIONS = [
250
255
  category: 'opt-in',
251
256
  type: 'standalone',
252
257
  scriptDir: 'dialtone_migrate_flex_to_stack',
258
+ // Injects a DtStack import, so it honors the --package override.
259
+ supportsPackageArg: true,
253
260
  detectPatterns: [
254
261
  /class="[^"]*d-d-flex[^"]*"/,
255
262
  ],
@@ -304,6 +311,8 @@ const MIGRATIONS = [
304
311
  category: 'opt-in',
305
312
  type: 'standalone',
306
313
  scriptDir: 'dialtone_migrate_typography',
314
+ // Injects a DtText import, so it honors the --package override.
315
+ supportsPackageArg: true,
307
316
  detectPatterns: [
308
317
  /class="[^"]*d-(?:headline|body|label)--(?:sm|md|lg|xl|xxl)/,
309
318
  /class="[^"]*d-(?:headline|body|label)-(?:small|medium|large)/,
@@ -371,6 +380,17 @@ async function writeMigrationMarker (cwd, id) {
371
380
  function parseArgs (args) {
372
381
  const cwdIndex = args.indexOf('--cwd');
373
382
  const onlyIndex = args.indexOf('--only');
383
+ const packageIndex = args.indexOf('--package');
384
+
385
+ // --package must be followed by an actual package name. Reject a missing value
386
+ // or a value that is really the next option (e.g. `--package --all`) so invalid
387
+ // input never reaches the alias confirmation or child-migration args.
388
+ const packageValue = packageIndex !== -1 ? args[packageIndex + 1] : undefined;
389
+ if (packageIndex !== -1 && (!packageValue || packageValue.startsWith('-'))) {
390
+ console.error('\n --package requires a package name (e.g. --package @dialpad/dialtone-next)\n');
391
+ process.exit(1);
392
+ }
393
+
374
394
  return {
375
395
  help: args.includes('--help'),
376
396
  dryRun: args.includes('--dry-run'),
@@ -378,12 +398,14 @@ function parseArgs (args) {
378
398
  noImport: args.includes('--no-import'),
379
399
  healthCheck: args.includes('--health-check'),
380
400
  all: args.includes('--all'),
401
+ includeOptIn: args.includes('--include-opt-in'),
381
402
  cwd: cwdIndex !== -1 && args[cwdIndex + 1]
382
403
  ? path.resolve(args[cwdIndex + 1])
383
404
  : process.cwd(),
384
405
  only: onlyIndex !== -1 && args[onlyIndex + 1]
385
406
  ? args[onlyIndex + 1].split(',').map(s => s.trim())
386
407
  : null,
408
+ packageName: packageIndex !== -1 ? packageValue : null,
387
409
  };
388
410
  }
389
411
 
@@ -400,7 +422,11 @@ Options:
400
422
  --yes Apply all changes without prompting
401
423
  --health-check Report migration status without modifying files
402
424
  --all Run all required migrations without selection prompt
425
+ --include-opt-in Also run opt-in migrations (with --all, or on its own = everything)
403
426
  --only <ids> Comma-separated list of migration IDs to run
427
+ --package <name> Package name to use for injected component imports
428
+ (default: @dialpad/dialtone-vue). Useful when running
429
+ Dialtone under a custom package alias (e.g. @dialpad/dialtone-next).
404
430
  --help Show help
405
431
 
406
432
  Available migration IDs (required):
@@ -413,6 +439,7 @@ Examples:
413
439
  npx dialtone-migrate # Interactive selection
414
440
  npx dialtone-migrate --health-check --cwd ./src # Check migration status
415
441
  npx dialtone-migrate --all --dry-run # Dry-run all required
442
+ npx dialtone-migrate --all --include-opt-in --yes # Run required + opt-in
416
443
  npx dialtone-migrate --only color-stops,hsl-to-oklch --yes
417
444
  `);
418
445
  }
@@ -792,6 +819,9 @@ async function runStandaloneMigration (migration, opts) {
792
819
  if (opts.dryRun) args.push('--dry-run');
793
820
  if (opts.autoYes) args.push('--yes');
794
821
  if (opts.noImport) args.push('--no-import');
822
+ if (opts.packageName && migration.supportsPackageArg) {
823
+ args.push('--package', opts.packageName);
824
+ }
795
825
 
796
826
  return new Promise((resolve, reject) => {
797
827
  const child = spawn(process.execPath, [scriptPath, ...args], {
@@ -872,8 +902,12 @@ async function main () {
872
902
  }
873
903
  return m;
874
904
  });
875
- } else if (opts.all) {
876
- selected = MIGRATIONS.filter(m => m.category === 'required');
905
+ } else if (opts.all || opts.includeOptIn) {
906
+ // --include-opt-in widens the selection to every migration; --all alone stays
907
+ // required-only. Either flag skips the interactive prompt.
908
+ selected = opts.includeOptIn
909
+ ? MIGRATIONS.slice()
910
+ : MIGRATIONS.filter(m => m.category === 'required');
877
911
  } else {
878
912
  selected = await selectMigrations(MIGRATIONS);
879
913
  }
@@ -885,6 +919,9 @@ async function main () {
885
919
 
886
920
  // Confirmation
887
921
  console.log(`\n Target directory: ${opts.cwd}`);
922
+ if (opts.packageName) {
923
+ console.log(` Import package: ${opts.packageName}`);
924
+ }
888
925
  console.log(` Migrations to run (${selected.length}):\n`);
889
926
  for (const m of selected) {
890
927
  const tag = m.category === 'required' ? '[REQUIRED]' : '[OPT-IN]';
@@ -0,0 +1,88 @@
1
+ /**
2
+ * dialtone-migrate (master orchestrator) tests.
3
+ *
4
+ * Exercises migration selection via the CLI in --dry-run against an empty temp
5
+ * directory, asserting which categories each flag selects.
6
+ * Run: node --test packages/dialtone-css/lib/build/js/dialtone_migrate/test.mjs
7
+ */
8
+
9
+ import { describe, it } from 'node:test';
10
+ import assert from 'node:assert/strict';
11
+ import { execFileSync } from 'node:child_process';
12
+ import fs from 'node:fs';
13
+ import os from 'node:os';
14
+ import path from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ const cli = path.join(path.dirname(fileURLToPath(import.meta.url)), 'index.mjs');
18
+
19
+ // Run the CLI in --dry-run against an empty temp dir and return stdout.
20
+ function runDryRun (flags) {
21
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'dlt-master-'));
22
+ try {
23
+ return execFileSync(
24
+ process.execPath,
25
+ [cli, '--dry-run', '--cwd', tmp, ...flags],
26
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
27
+ );
28
+ } finally {
29
+ fs.rmSync(tmp, { recursive: true, force: true });
30
+ }
31
+ }
32
+
33
+ function selectedCount (output) {
34
+ const m = output.match(/Migrations to run \((\d+)\)/);
35
+ return m ? Number(m[1]) : null;
36
+ }
37
+
38
+ describe('migration selection', () => {
39
+ it('--all selects the required migrations only (excludes opt-in)', () => {
40
+ const output = runDryRun(['--all']);
41
+ assert.equal(selectedCount(output), 12);
42
+ assert.ok(!output.includes('[OPT-IN]'), '--all must not select opt-in migrations');
43
+ });
44
+
45
+ it('--all --include-opt-in selects every migration', () => {
46
+ const output = runDryRun(['--all', '--include-opt-in']);
47
+ assert.equal(selectedCount(output), 20);
48
+ assert.ok(output.includes('[OPT-IN]'), 'opt-in migrations should be selected');
49
+ });
50
+
51
+ it('--include-opt-in on its own also selects every migration', () => {
52
+ const output = runDryRun(['--include-opt-in']);
53
+ assert.equal(selectedCount(output), 20);
54
+ });
55
+ });
56
+
57
+ describe('--package validation', () => {
58
+ // Run the CLI with the given args and return { status, stderr }; never throws.
59
+ function run (extraArgs) {
60
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'dlt-master-'));
61
+ try {
62
+ execFileSync(process.execPath, [cli, '--dry-run', '--cwd', tmp, ...extraArgs],
63
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
64
+ return { status: 0, stderr: '' };
65
+ } catch (err) {
66
+ return { status: err.status, stderr: String(err.stderr ?? '') };
67
+ } finally {
68
+ fs.rmSync(tmp, { recursive: true, force: true });
69
+ }
70
+ }
71
+
72
+ it('rejects --package with no value', () => {
73
+ const { status, stderr } = run(['--all', '--package']);
74
+ assert.equal(status, 1);
75
+ assert.match(stderr, /--package requires a package name/);
76
+ });
77
+
78
+ it('rejects --package followed by another option (--package --all)', () => {
79
+ const { status, stderr } = run(['--package', '--all']);
80
+ assert.equal(status, 1);
81
+ assert.match(stderr, /--package requires a package name/);
82
+ });
83
+
84
+ it('accepts a valid package name', () => {
85
+ const { status } = run(['--all', '--package', '@dialpad/dialtone-next']);
86
+ assert.equal(status, 0);
87
+ });
88
+ });
@@ -1030,7 +1030,7 @@ async function processFile(filePath, options) {
1030
1030
 
1031
1031
  // Auto-inject DtStack import before writing; fall back to manual instructions if needed.
1032
1032
  // Skipped entirely when --no-import is set (e.g. DtStack is globally registered).
1033
- const importCheck = options.noImport ? null : detectMissingStackImport(newContent, changes > 0);
1033
+ const importCheck = options.noImport ? null : detectMissingStackImport(newContent, changes > 0, options.package);
1034
1034
  let finalContent = newContent;
1035
1035
  if (importCheck?.needsImport) {
1036
1036
  const injected = injectComponentImport(newContent, 'DtStack', importCheck.suggestedPath);
@@ -1102,9 +1102,10 @@ async function cleanupMarkers(filePath, options) {
1102
1102
  * Check if a file needs DtStack import
1103
1103
  * @param {string} content - Full file content
1104
1104
  * @param {boolean} usesStack - Whether file has <dt-stack> in template
1105
+ * @param {string} [packageName] - Explicit package to import from (overrides detection)
1105
1106
  * @returns {object|null} - Detection result with suggested import path, or null if import exists
1106
1107
  */
1107
- function detectMissingStackImport(content, usesStack) {
1108
+ function detectMissingStackImport(content, usesStack, packageName) {
1108
1109
  if (!usesStack) return null;
1109
1110
 
1110
1111
  // Check if DtStack is already imported
@@ -1112,7 +1113,7 @@ function detectMissingStackImport(content, usesStack) {
1112
1113
  if (hasImport) return null;
1113
1114
 
1114
1115
  // Analyze existing imports to suggest appropriate path
1115
- const importPath = detectImportPattern(content);
1116
+ const importPath = detectImportPattern(content, packageName);
1116
1117
 
1117
1118
  return {
1118
1119
  needsImport: true,
@@ -1124,9 +1125,17 @@ function detectMissingStackImport(content, usesStack) {
1124
1125
  /**
1125
1126
  * Detect import pattern from existing imports in file
1126
1127
  * @param {string} content - File content
1128
+ * @param {string} [packageName] - Explicit package name (e.g. @dialpad/dialtone-next).
1129
+ * When provided, it is used verbatim, overriding the heuristics below. This lets
1130
+ * consumers running Dialtone under a custom package alias inject the correct import.
1127
1131
  * @returns {string} - Suggested import path
1128
1132
  */
1129
- function detectImportPattern(content) {
1133
+ function detectImportPattern(content, packageName) {
1134
+ // Explicit override: honor the caller-provided package name.
1135
+ if (packageName) {
1136
+ return packageName;
1137
+ }
1138
+
1130
1139
  // Check for @/ alias (absolute from package root)
1131
1140
  if (content.includes('from \'@/components/')) {
1132
1141
  return '@/components/stack';
@@ -1257,6 +1266,7 @@ function parseArgs() {
1257
1266
  showOutline: false, // Add migration marker for visual debugging
1258
1267
  removeOutline: false, // Remove migration markers (cleanup mode)
1259
1268
  noImport: false, // Skip import injection (for apps that globally register DtStack)
1269
+ package: null, // Override the package name used for injected DtStack imports
1260
1270
  };
1261
1271
 
1262
1272
  for (let i = 0; i < args.length; i++) {
@@ -1285,6 +1295,9 @@ Options:
1285
1295
  --show-outline Add data-migrate-outline attribute for visual debugging
1286
1296
  --remove-outline Remove data-migrate-outline attributes after review
1287
1297
  --no-import Skip import injection (use when DtStack is globally registered)
1298
+ --package <name> Package name for injected DtStack imports
1299
+ (default: @dialpad/dialtone-vue when a Dialtone import is detected).
1300
+ Use when Dialtone runs under a custom alias, e.g. @dialpad/dialtone-next.
1288
1301
  --help, -h Show help
1289
1302
 
1290
1303
  Post-Migration Steps:
@@ -1333,6 +1346,15 @@ Examples:
1333
1346
  options.removeOutline = true;
1334
1347
  } else if (arg === '--no-import') {
1335
1348
  options.noImport = true;
1349
+ } else if (arg === '--package') {
1350
+ // Reject a missing value or one that is really the next option (e.g.
1351
+ // `--package --yes`) so an invalid alias is never recorded.
1352
+ const value = args[i + 1];
1353
+ if (!value || value.startsWith('-')) {
1354
+ console.error('\n --package requires a package name (e.g. --package @dialpad/dialtone-next)\n');
1355
+ process.exit(1);
1356
+ }
1357
+ options.package = args[++i];
1336
1358
  } else if (arg === '--file' && args[i + 1]) {
1337
1359
  const filePath = args[++i];
1338
1360
  options.files.push(filePath);
@@ -1430,6 +1452,7 @@ async function main() {
1430
1452
  showOutline: options.showOutline,
1431
1453
  validate: options.validate,
1432
1454
  noImport: options.noImport,
1455
+ package: options.package,
1433
1456
  });
1434
1457
  }
1435
1458
 
@@ -1235,11 +1235,20 @@ async function validateAndResolveFiles (filePaths, extensions) {
1235
1235
  // Import detection (ported from flex-to-stack)
1236
1236
  //------------------------------------------------------------------------------
1237
1237
 
1238
- export function detectImportPathFor (content) {
1239
- return detectImportPattern(content);
1238
+ export function detectImportPathFor (content, packageName) {
1239
+ return detectImportPattern(content, packageName);
1240
1240
  }
1241
1241
 
1242
- function detectImportPattern (content) {
1242
+ /**
1243
+ * @param {string} content - File content
1244
+ * @param {string} [packageName] - Explicit package name (e.g. @dialpad/dialtone-next).
1245
+ * When provided, it is used verbatim, overriding the heuristics below. This lets
1246
+ * consumers running Dialtone under a custom package alias inject the correct import.
1247
+ */
1248
+ function detectImportPattern (content, packageName) {
1249
+ // Explicit override: honor the caller-provided package name.
1250
+ if (packageName) return packageName;
1251
+
1243
1252
  if (content.includes('from \'@/components/')) return '@/components/text';
1244
1253
  if (content.includes('from \'./\'')) return './';
1245
1254
  if (content.includes('from \'@dialpad/dialtone-vue') || content.includes('from \'@dialpad/dialtone-icons')) {
@@ -1248,13 +1257,13 @@ function detectImportPattern (content) {
1248
1257
  return '@/components/text';
1249
1258
  }
1250
1259
 
1251
- function detectMissingDtTextImport (content, usesText) {
1260
+ function detectMissingDtTextImport (content, usesText, packageName) {
1252
1261
  if (!usesText) return null;
1253
1262
  const hasImport = /import\s+(?:\{[^}]*\bDtText\b[^}]*\}|DtText)\s+from/.test(content);
1254
1263
  if (hasImport) return null;
1255
1264
  return {
1256
1265
  needsImport: true,
1257
- suggestedPath: detectImportPattern(content),
1266
+ suggestedPath: detectImportPattern(content, packageName),
1258
1267
  hasComponentsObject: /components:\s*\{/.test(content),
1259
1268
  };
1260
1269
  }
@@ -1537,7 +1546,7 @@ async function processFile (filePath, options) {
1537
1546
  const addedDtText = afterCount > beforeCount;
1538
1547
  // Auto-inject DtText import before writing; fall back to manual instructions if needed.
1539
1548
  // Skipped entirely when --no-import is set (e.g. DtText is globally registered).
1540
- const importCheck = options.noImport ? null : detectMissingDtTextImport(transformed, addedDtText);
1549
+ const importCheck = options.noImport ? null : detectMissingDtTextImport(transformed, addedDtText, options.package);
1541
1550
 
1542
1551
  let finalContent = transformed;
1543
1552
  if (importCheck?.needsImport) {
@@ -1580,6 +1589,7 @@ function parseArgs () {
1580
1589
  removeMarkers: false,
1581
1590
  validate: false,
1582
1591
  noImport: false, // Skip import injection (for apps that globally register DtText)
1592
+ package: null, // Override the package name used for injected DtText imports
1583
1593
  };
1584
1594
 
1585
1595
  for (let i = 0; i < args.length; i++) {
@@ -1600,6 +1610,9 @@ Options:
1600
1610
  --validate Read-only mode: scan existing <dt-text> for prop bugs
1601
1611
  (object syntax, invalid values, mixed CSS classes)
1602
1612
  --no-import Skip import injection (use when DtText is globally registered)
1613
+ --package <name> Package name for injected DtText imports
1614
+ (default: @dialpad/dialtone-vue when a Dialtone import is detected).
1615
+ Use when Dialtone runs under a custom alias, e.g. @dialpad/dialtone-next.
1603
1616
  --help, -h Show help
1604
1617
 
1605
1618
  Examples:
@@ -1630,6 +1643,15 @@ Post-Migration Steps:
1630
1643
  options.validate = true;
1631
1644
  } else if (arg === '--no-import') {
1632
1645
  options.noImport = true;
1646
+ } else if (arg === '--package') {
1647
+ // Reject a missing value or one that is really the next option (e.g.
1648
+ // `--package --yes`) so an invalid alias is never recorded.
1649
+ const value = args[i + 1];
1650
+ if (!value || value.startsWith('-')) {
1651
+ console.error('\n --package requires a package name (e.g. --package @dialpad/dialtone-next)\n');
1652
+ process.exit(1);
1653
+ }
1654
+ options.package = args[++i];
1633
1655
  }
1634
1656
  }
1635
1657
 
@@ -7,6 +7,11 @@
7
7
 
8
8
  import { describe, it } from 'node:test';
9
9
  import assert from 'node:assert/strict';
10
+ import { execFileSync } from 'node:child_process';
11
+ import fs from 'node:fs';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
10
15
  import {
11
16
  transformContent,
12
17
  detectImportPathFor,
@@ -729,6 +734,83 @@ describe('import detection', () => {
729
734
  const path = detectImportPathFor(content);
730
735
  assert.equal(path, '@/components/text');
731
736
  });
737
+
738
+ it('uses the explicit package name when provided, overriding detection', () => {
739
+ const content = `<script>\nimport { DtButton } from '@/components/button';\n</script>`;
740
+ const path = detectImportPathFor(content, '@dialpad/dialtone-next');
741
+ assert.equal(path, '@dialpad/dialtone-next');
742
+ });
743
+
744
+ it('explicit package name applies even when no imports are present', () => {
745
+ const content = `<template><p class="d-headline--md">x</p></template>`;
746
+ const path = detectImportPathFor(content, '@dialpad/dialtone-next');
747
+ assert.equal(path, '@dialpad/dialtone-next');
748
+ });
749
+ });
750
+
751
+ // ---------------------------------------------------------------------------
752
+ // --package end-to-end: prove the flag reaches the injected import, both via
753
+ // the standalone CLI and forwarded through the master orchestrator.
754
+ // ---------------------------------------------------------------------------
755
+
756
+ describe('--package end-to-end', () => {
757
+ const dir = path.dirname(fileURLToPath(import.meta.url));
758
+ const typographyCli = path.join(dir, 'index.mjs');
759
+ const masterCli = path.join(dir, '..', 'dialtone_migrate', 'index.mjs');
760
+
761
+ // Uses <script setup> so the import auto-injects, a local @/components import so
762
+ // that without --package detection would resolve to @/components/text (not the
763
+ // package), and a d-headline-- class so a <dt-text> is added (triggering the import).
764
+ const SOURCE = [
765
+ '<script setup>',
766
+ `import { DtButton } from '@/components/button';`,
767
+ '</script>',
768
+ '<template>',
769
+ ' <p class="d-headline--md">Title</p>',
770
+ '</template>',
771
+ '',
772
+ ].join('\n');
773
+
774
+ function migrate (argv) {
775
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'dlt-typo-'));
776
+ const file = path.join(tmp, 'Sample.vue');
777
+ fs.writeFileSync(file, SOURCE, 'utf8');
778
+ try {
779
+ execFileSync(process.execPath, argv(tmp), { stdio: 'ignore' });
780
+ return fs.readFileSync(file, 'utf8');
781
+ } finally {
782
+ fs.rmSync(tmp, { recursive: true, force: true });
783
+ }
784
+ }
785
+
786
+ it('standalone CLI injects the explicit package into the generated import', () => {
787
+ const output = migrate(tmp => [
788
+ typographyCli, '--cwd', tmp, '--yes', '--package', '@dialpad/dialtone-next',
789
+ ]);
790
+ assert.match(output, /import \{ DtText \} from '@dialpad\/dialtone-next';/);
791
+ });
792
+
793
+ it('master orchestrator forwards --package to the typography child', () => {
794
+ // The import lands on @dialpad/dialtone-next only if the master forwarded the
795
+ // flag; otherwise detection would pick @/components/text from the existing import.
796
+ const output = migrate(tmp => [
797
+ masterCli, '--only', 'typography', '--yes', '--cwd', tmp, '--package', '@dialpad/dialtone-next',
798
+ ]);
799
+ assert.match(output, /import \{ DtText \} from '@dialpad\/dialtone-next';/);
800
+ });
801
+
802
+ it('rejects --package followed by another option (--package --yes)', () => {
803
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'dlt-typo-'));
804
+ try {
805
+ assert.throws(
806
+ () => execFileSync(process.execPath, [typographyCli, '--cwd', tmp, '--package', '--yes'],
807
+ { stdio: ['ignore', 'ignore', 'pipe'] }),
808
+ err => err.status === 1 && /--package requires a package name/.test(String(err.stderr)),
809
+ );
810
+ } finally {
811
+ fs.rmSync(tmp, { recursive: true, force: true });
812
+ }
813
+ });
732
814
  });
733
815
 
734
816
  describe('--remove-markers', () => {
@@ -17000,6 +17000,7 @@
17000
17000
  --dt-layout-75: calc(var(--dt-layout-base) * 0.75); /* 48px */
17001
17001
  --dt-layout-50: calc(var(--dt-layout-base) * 0.5); /* 32px */
17002
17002
  --dt-layout-25: calc(var(--dt-layout-base) * 0.25); /* 16px */
17003
+ --dt-layout-0: calc(var(--dt-layout-base) * 0); /* 0px */
17003
17004
  --dt-spacing-800: calc(var(--dt-spacing-base) * 8); /* 64px */
17004
17005
  --dt-spacing-750: calc(var(--dt-spacing-base) * 7.5); /* 60px */
17005
17006
  --dt-spacing-700: calc(var(--dt-spacing-base) * 7); /* 56px */