Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/skills/dt-migrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ description: "Run Dialtone migration tools for token and utility renames. Use '/
| `color-stops` | Renames irregular color stops (250, 350, 425, etc.) to standard 12-stop scale | `npx dialtone-migration-helper` โ†’ "color stops" |
| `base-to-semantic` | Replaces base color utilities and CSS tokens with semantic equivalents | `npx dialtone-migration-helper` โ†’ "base to semantic" |
| `space-to-size` | Renames `var(--dt-space-*)` to `var(--dt-size-*)` | `npx dialtone-migration-helper` โ†’ "space to size" |
| `size-to-layout` | Routes `var(--dt-size-*)` to `--dt-spacing-*` / `--dt-layout-*` / `--dt-size-border-*` / `--dt-size-radius-*` based on CSS property context. Covers off-scale pixel-indexed exceptions (1/2/8/20/24 px โ†’ `--dt-layout-Npx`) in layout context | `npx dialtone-migration-helper` โ†’ "size to layout" |
| `utility-class-to-token-stops` | Rewrites legacy pixel-indexed utility class names (`d-h16`, `d-p8`) to token-stop-based names (`d-h-25`, `d-p-100`). Covers off-scale pixel-indexed exceptions (`d-w1` โ†’ `d-w-1px`, `d-h24` โ†’ `d-h-24px`, etc.) | `npx dialtone-migration-helper` โ†’ "utility class to token stops" |
| `hsl-to-oklch` | Migrates consumer HSL channel variable patterns to OKLCH relative color syntax or plain `var()` | `npx dialtone-migration-helper` โ†’ "hsl to oklch" |

## Usage
Expand Down
5 changes: 5 additions & 0 deletions apps/dialtone-documentation/docs/_data/width-height.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
{
"percentage": [10, 15, 20, 25, 30, 33, 40, 50, 60, 66, 70, 75, 80, 85, 90, 100],
"layout": [
{ "stop": "1px", "px": 1 },
{ "stop": "2px", "px": 2 },
{ "stop": "8px", "px": 8 },
{ "stop": "25", "px": 16 },
{ "stop": "20px", "px": 20 },
{ "stop": "24px", "px": 24 },
{ "stop": "50", "px": 32 },
{ "stop": "75", "px": 48 },
{ "stop": "100", "px": 64 },
Expand Down
3 changes: 3 additions & 0 deletions apps/dialtone-documentation/docs/utilities/sizing/height.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ keywords: ["size", "tall", "vh", "viewport height", "block size", "block-size"]

Use `d-h-{stop}` to set a fixed height for an element using layout token stops. The hyphen before the number indicates a layout token reference, e.g. `d-h-100` outputs `block-size: var(--dt-layout-100)` (64px).

> [!INFO] Scale-indexed vs pixel-indexed stops
> Bare integer stops (`25`, `50`, `100`, โ€ฆ) are scale-indexed on the 64px base โ€” `value_in_px = stop ร— 64 / 100`, so `25` = 16px and `100` = 64px. Stops with a `px` suffix (`1px`, `2px`, `8px`, `20px`, `24px`) are off-scale exceptions that encode the literal pixel value.

```vue demo
<!-- @class d-d-block d-bgc-secondary d-w100p d-hmn-400 -->
<div v-dt-scrollbar:never class="d-bar8 d-d-flex d-g-200 d-bgc-secondary d-hmx-800">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ keywords: ["maximum height", "mxh", "max block size", "max block-size"]

Use `d-hmx-{stop}` to set a fixed maximum height for an element using layout token stops. The hyphen before the number indicates a layout token reference, e.g. `d-hmx-100` outputs `max-block-size: var(--dt-layout-100)` (64px). This can be combined with `d-h{n}p` and `d-hmn-{stop}` to have an element fill a certain height range.

> [!INFO] Scale-indexed vs pixel-indexed stops
> Bare integer stops (`25`, `50`, `100`, โ€ฆ) are scale-indexed on the 64px base โ€” `value_in_px = stop ร— 64 / 100`, so `25` = 16px and `100` = 64px. Stops with a `px` suffix (`1px`, `2px`, `8px`, `20px`, `24px`) are off-scale exceptions that encode the literal pixel value.

```vue demo
<dt-stack direction="row" gap="200" align="start" justify="center" class="d-w100p">
<dt-stack direction="row" align="center" justify="center" class="d-py-200 d-px-100 d-w100p d-h-100 d-hmx100p d-bgc-moderate d-bar4 d-ta-center">1</dt-stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ keywords: ["maximum width", "mxw", "max inline size", "max inline-size"]

Use `d-wmx-{stop}` to set a fixed maximum width for an element using layout token stops. The hyphen before the number indicates a layout token reference, e.g. `d-wmx-100` outputs `max-inline-size: var(--dt-layout-100)` (64px). This can be combined with `d-w{n}p` and `d-wmn-{stop}` to have an element fill a certain width range.

> [!INFO] Scale-indexed vs pixel-indexed stops
> Bare integer stops (`25`, `50`, `100`, โ€ฆ) are scale-indexed on the 64px base โ€” `value_in_px = stop ร— 64 / 100`, so `25` = 16px and `100` = 64px. Stops with a `px` suffix (`1px`, `2px`, `8px`, `20px`, `24px`) are off-scale exceptions that encode the literal pixel value.

```vue demo
<dt-stack direction="row" justify="center" gap="200" class="d-w100p">
<dt-stack direction="row" align="center" justify="center" class="d-py-200 d-px-100 d-w100p d-h-100 d-wmx-100 d-bgc-moderate d-bar4 d-ta-center">1</dt-stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ keywords: ["minimum height", "mnh", "min block size", "min block-size"]

Use `d-hmn-{stop}` to set a fixed minimum height for an element using layout token stops. The hyphen before the number indicates a layout token reference, e.g. `d-hmn-100` outputs `min-block-size: var(--dt-layout-100)` (64px). This can be combined with `d-h{n}p` and `d-hmx-{stop}` to have an element fill a certain height range.

> [!INFO] Scale-indexed vs pixel-indexed stops
> Bare integer stops (`25`, `50`, `100`, โ€ฆ) are scale-indexed on the 64px base โ€” `value_in_px = stop ร— 64 / 100`, so `25` = 16px and `100` = 64px. Stops with a `px` suffix (`1px`, `2px`, `8px`, `20px`, `24px`) are off-scale exceptions that encode the literal pixel value.

```vue demo
<dt-stack direction="row" gap="200" align="start" justify="center" class="d-w100p">
<dt-stack direction="row" align="center" justify="center" class="d-py-200 d-px-100 d-w-100 d-h-75 d-hmn-100 d-bgc-moderate d-bar4 d-ta-center">1</dt-stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ keywords: ["minimum width", "mnw", "min inline size", "min inline-size"]

Use `d-wmn-{stop}` to set a fixed minimum width for an element using layout token stops. The hyphen before the number indicates a layout token reference, e.g. `d-wmn-100` outputs `min-inline-size: var(--dt-layout-100)` (64px). This can be combined with `d-w{n}p` and `d-wmx-{stop}` to have an element fill a certain width range.

> [!INFO] Scale-indexed vs pixel-indexed stops
> Bare integer stops (`25`, `50`, `100`, โ€ฆ) are scale-indexed on the 64px base โ€” `value_in_px = stop ร— 64 / 100`, so `25` = 16px and `100` = 64px. Stops with a `px` suffix (`1px`, `2px`, `8px`, `20px`, `24px`) are off-scale exceptions that encode the literal pixel value.

```vue demo
<dt-stack direction="row" justify="center" gap="200" class="d-w100p">
<dt-stack direction="row" align="center" justify="center" class="d-py-200 d-px-100 d-size-100 d-wmn-100 d-bgc-moderate d-bar4">1</dt-stack>
Expand Down
3 changes: 3 additions & 0 deletions apps/dialtone-documentation/docs/utilities/sizing/size.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Size utilities set both `inline-size` (width) and `block-size` (height) at once.

Use `d-size-{stop}` to set both width and height using layout token stops. The hyphen before the number indicates a layout token reference, e.g. `d-size-100` outputs both `inline-size: var(--dt-layout-100)` and `block-size: var(--dt-layout-100)` (64px).

> [!INFO] Scale-indexed vs pixel-indexed stops
> Bare integer stops (`25`, `50`, `100`, โ€ฆ) are scale-indexed on the 64px base โ€” `value_in_px = stop ร— 64 / 100`, so `25` = 16px and `100` = 64px. Stops with a `px` suffix (`1px`, `2px`, `8px`, `20px`, `24px`) are off-scale exceptions that encode the literal pixel value.

```vue demo
<div v-dt-scrollbar:never class="d-bar8 d-d-flex d-bgc-secondary d-w100p d-hmx-500 d-ta-center">
<dt-stack gap="100" align="start">
Expand Down
3 changes: 3 additions & 0 deletions apps/dialtone-documentation/docs/utilities/sizing/width.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ keywords: ["size", "wide", "vw", "viewport width", "inline size", "inline-size"]

Use `d-w-{stop}` to set a fixed width for an element using layout token stops. The hyphen before the number indicates a layout token reference, e.g. `d-w-100` outputs `inline-size: var(--dt-layout-100)` (64px).

> [!INFO] Scale-indexed vs pixel-indexed stops
> Bare integer stops (`25`, `50`, `100`, โ€ฆ) are scale-indexed on the 64px base โ€” `value_in_px = stop ร— 64 / 100`, so `25` = 16px and `100` = 64px. Stops with a `px` suffix (`1px`, `2px`, `8px`, `20px`, `24px`) are off-scale exceptions that encode the literal pixel value.

```vue demo
<div v-dt-scrollbar:never class="d-bar8 d-d-flex d-bgc-secondary d-w100p d-hmx-500 d-ta-center">
<dt-stack gap="100" align="start">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ const SPACING_MAP = {
// For tokens used in layout context (width, height, etc.)
// Exact matches are labeled; nearest-neighbor approximations note the delta.
const LAYOUT_MAP = {
// Off-scale pixel-indexed exceptions (DLT-3330) โ€” exact matches via Npx stops.
// Old --dt-size-N stop at these pixel values has no scale-indexed layout equivalent;
// route to the off-scale Npx token in layout-property context.
100: '1px', // 1px
200: '2px', // 2px
400: '8px', // 8px
525: '20px', // 20px
550: '24px', // 24px
// Exact scale matches
500: '25', // 16px
600: '50', // 32px
Expand Down Expand Up @@ -144,6 +152,8 @@ export default {
'eg. padding: var(--dt-size-400) โ†’ padding: var(--dt-spacing-100)\n' +
'- Layout properties (width, height, min/max, flex-basis) โ†’ var(--dt-layout-*)\n\t' +
'eg. width: var(--dt-size-700) โ†’ width: var(--dt-layout-100)\n' +
'- Off-scale layout exceptions: width: var(--dt-size-400) โ†’ width: var(--dt-layout-8px)\n\t' +
'(covers 100/200/400/525/550 stops โ†’ 1px/2px/8px/20px/24px in layout context only)\n' +
'- Percentage tokens โ†’ var(--dt-layout-*-percent)\n\t' +
'eg. var(--dt-size-100-percent) โ†’ var(--dt-layout-100-percent)\n' +
'- Tokens exceeding the layout scale (>1024px) are converted to raw rem with a TODO comment.\n' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
// e.g. d-h16 โ†’ d-h-25, d-p8 โ†’ d-p-100, d-m8 โ†’ d-m-100

// Sizing: pixel value โ†’ layout token stop
// MUST STAY IN SYNC with WIDTH_HEIGHTS_LAYOUT in dialtone-css/postcss/constants.cjs
// MUST STAY IN SYNC with LAYOUT_STOPS in dialtone-css/postcss/constants.cjs
// Off-scale pixel-indexed exceptions (1, 2, 8, 20, 24) map to the Npx stops
// introduced in DLT-3330; scale-indexed values (16+) map to the 64px-base stops.
const SIZING_MAP = {
// Off-scale pixel-indexed exceptions
1: '1px', 2: '2px', 8: '8px', 20: '20px', 24: '24px',
// Scale-indexed stops (64px base)
16: '25', 32: '50', 48: '75', 64: '100', 80: '125', 96: '150',
112: '175', 128: '200', 160: '250', 192: '300', 224: '350', 256: '400',
288: '450', 320: '500', 352: '550', 384: '600', 416: '650', 448: '700',
Expand All @@ -13,10 +18,8 @@ const SIZING_MAP = {
992: '1550', 1024: '1600',
};

// Small sizing values (0-12px) that map to spacing tokens, not layout tokens.
// These old classes (d-h0, d-h1, d-h2, etc.) don't have a layout-stop equivalent.
// They should be migrated to use the spacing token directly in CSS rather than a utility class,
// or left as-is since the old classes still work.
// Remaining off-scale sizing values without a layout-token equivalent (4, 6, 10, 12, 14)
// are left on the Tier 1 calc-based legacy path and pass through unchanged.

// Spacing: pixel value โ†’ spacing token stop
// MUST STAY IN SYNC with GAP_SPACES_SPACING / MARGIN_SIZES_SPACING / PADDING_SIZES_SPACING in dialtone-css/postcss/constants.cjs
Expand Down Expand Up @@ -51,6 +54,7 @@ export default {
description:
'Migrates pixel-based utility class names to token-stop-based names.\n' +
'- Sizing: d-h16 โ†’ d-h-25, d-w64 โ†’ d-w-100, d-hmn96 โ†’ d-hmn-150\n' +
'- Off-scale sizing: d-w1 โ†’ d-w-1px, d-h24 โ†’ d-h-24px (pixel-indexed exceptions)\n' +
'- Margin: d-m8 โ†’ d-m-100, d-mt16 โ†’ d-mt-200, d-mtn8 โ†’ d-mt-n100\n' +
'- Padding: d-p8 โ†’ d-p-100, d-pt16 โ†’ d-pt-200\n' +
'- Gap: d-g8 โ†’ d-g-100, d-rg16 โ†’ d-rg-200\n' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
<div :style="{ height: 'var(--dt-size-800)' }">size-800 on height โ†’ layout-200</div>
<div :style="{ maxWidth: 'var(--dt-size-1000)' }">size-1000 on max-width โ†’ layout-800</div>

<!-- Off-scale pixel-indexed exceptions (DLT-3330) โ€” layout context only -->
<div :style="{ width: 'var(--dt-size-100)' }">size-100 on width โ†’ layout-1px</div>
<div :style="{ height: 'var(--dt-size-200)' }">size-200 on height โ†’ layout-2px</div>
<div :style="{ minInlineSize: 'var(--dt-size-400)' }">size-400 on min-inline-size โ†’ layout-8px</div>
<div :style="{ maxWidth: 'var(--dt-size-525)' }">size-525 on max-width โ†’ layout-20px</div>
<div :style="{ blockSize: 'var(--dt-size-550)' }">size-550 on block-size โ†’ layout-24px</div>

<!-- Skip: already-migrated tokens -->
<div :style="{ width: 'var(--dt-layout-100)' }">already layout-100</div>
<p :style="{ padding: 'var(--dt-spacing-100)' }">already spacing-100</p>
Expand Down Expand Up @@ -113,6 +120,15 @@
inline-size: var(--dt-size-1100); /* โ†’ layout-1600 (1024px) */
}

/* off-scale pixel-indexed exceptions (DLT-3330) โ€” only route in layout context */
.test-width-off-scale {
inline-size: var(--dt-size-100); /* โ†’ layout-1px (1px) */
block-size: var(--dt-size-200); /* โ†’ layout-2px (2px) */
min-inline-size: var(--dt-size-400); /* โ†’ layout-8px (8px) */
max-inline-size: var(--dt-size-525); /* โ†’ layout-20px (20px) */
block-size: var(--dt-size-550); /* โ†’ layout-24px (24px) */
}

/* height */
.test-height {
block-size: var(--dt-size-700);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import config from '../configs/size-to-layout.mjs';
import { applyConfig } from './helpers.mjs';

const apply = (input) => applyConfig(config, input);

describe('size-to-layout config', () => {
// โ”€โ”€โ”€ Off-scale pixel-indexed exceptions (DLT-3330) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
//
// Layout-property context only: old --dt-size-N stop at these pixel values
// maps to the off-scale --dt-layout-Npx token.
// 100 โ†’ 1px, 200 โ†’ 2px, 400 โ†’ 8px, 525 โ†’ 20px, 550 โ†’ 24px

describe('off-scale exceptions โ€” layout context routes to --dt-layout-Npx', () => {
const offScaleCases = [
['width', '--dt-size-100', '--dt-layout-1px'],
['height', '--dt-size-200', '--dt-layout-2px'],
['min-inline-size', '--dt-size-400', '--dt-layout-8px'],
['max-width', '--dt-size-525', '--dt-layout-20px'],
['block-size', '--dt-size-550', '--dt-layout-24px'],
['inline-size', '--dt-size-100', '--dt-layout-1px'],
['min-height', '--dt-size-400', '--dt-layout-8px'],
['flex-basis', '--dt-size-400', '--dt-layout-8px'],
];

for (const [prop, from, to] of offScaleCases) {
it(`${prop}: var(${from}) โ†’ var(${to})`, () => {
const result = apply(`.x { ${prop}: var(${from}); }`);
assert.equal(result, `.x { ${prop}: var(${to}); }`);
});
}
});

describe('off-scale exceptions โ€” spacing context STILL routes to --dt-spacing-* (regression guard)', () => {
// These are the same stops, but used in spacing-property context.
// The LAYOUT_MAP additions must NOT leak into spacing routing.
const spacingRegressionCases = [
['padding', '--dt-size-100', '--dt-spacing-1'],
['margin', '--dt-size-200', '--dt-spacing-25'],
['gap', '--dt-size-400', '--dt-spacing-100'],
['padding-block', '--dt-size-525', '--dt-spacing-250'],
['margin-inline', '--dt-size-550', '--dt-spacing-300'],
['inset', '--dt-size-400', '--dt-spacing-100'],
];

for (const [prop, from, to] of spacingRegressionCases) {
it(`${prop}: var(${from}) โ†’ var(${to})`, () => {
const result = apply(`.x { ${prop}: var(${from}); }`);
assert.equal(result, `.x { ${prop}: var(${to}); }`);
});
}
});

// โ”€โ”€โ”€ Existing scale-indexed behavior unchanged (smoke tests) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

describe('scale-indexed stops โ€” unchanged', () => {
const scaleCases = [
['width', '--dt-size-500', '--dt-layout-25'], // 16px
['width', '--dt-size-700', '--dt-layout-100'], // 64px
['width', '--dt-size-1100', '--dt-layout-1600'], // 1024px
['padding', '--dt-size-700', '--dt-spacing-800'], // 64px
['margin', '--dt-size-500', '--dt-spacing-200'], // 16px
];

for (const [prop, from, to] of scaleCases) {
it(`${prop}: var(${from}) โ†’ var(${to})`, () => {
const result = apply(`.x { ${prop}: var(${from}); }`);
assert.equal(result, `.x { ${prop}: var(${to}); }`);
});
}
});

// โ”€โ”€โ”€ Logical properties and custom property name heuristics โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

describe('off-scale โ€” custom property name heuristics', () => {
it('--panel-width: var(--dt-size-400) โ†’ --dt-layout-8px', () => {
const result = apply(`.x { --panel-width: var(--dt-size-400); }`);
assert.equal(result, `.x { --panel-width: var(--dt-layout-8px); }`);
});

it('--badge-padding-x: var(--dt-size-400) โ†’ --dt-spacing-100 (stays spacing)', () => {
const result = apply(`.x { --badge-padding-x: var(--dt-size-400); }`);
assert.equal(result, `.x { --badge-padding-x: var(--dt-spacing-100); }`);
});
});
});
Loading
Loading