Skip to content

Commit

Permalink
feat(design): support dimension type object (#2703)
Browse files Browse the repository at this point in the history
## Proposed change

- feat(design): support dimension type object in shadow and typography
structures.
- feat(design): add inset property to shadow

Due to specification evolution:
- typography spec: https://tr.designtokens.org/format/#typography
- shadow spec: https://tr.designtokens.org/format/#shadow

<!--
Please include a summary of the changes and the related issue.
Please also include relevant motivation and context.
-->

## Related issues

<!--
Please make sure to follow the [contribution
guidelines](https://github.com/amadeus-digital/Otter/blob/main/CONTRIBUTING.md)
-->

*- No issue associated -*

<!-- * 🐛 Fix #issue -->
<!-- * 🐛 Fix resolves #issue -->
<!-- * 🚀 Feature #issue -->
<!-- * 🚀 Feature resolves #issue -->
<!-- * :octocat: Pull Request #issue -->
  • Loading branch information
kpanot authored Jan 20, 2025
2 parents 734a973 + 1dfeb3f commit 2c537d9
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 40 deletions.
92 changes: 83 additions & 9 deletions packages/@o3r/design/schemas/design-token.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
}
],
"definitions": {
"tokenReference": {
"type": "string",
"pattern": "^\\{[^}]+\\}$"
},
"tokenNode": {
"oneOf": [
{
Expand Down Expand Up @@ -198,6 +202,21 @@
}
}
},
"tokenTypeDimensionValue": {
"type": "object",
"required": [
"unit",
"value"
],
"properties": {
"value": {
"type": "number"
},
"unit": {
"type": "string"
}
}
},
"tokenTypeDimension": {
"type": "object",
"required": [
Expand All @@ -206,7 +225,14 @@
],
"properties": {
"$value": {
"type": "string"
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/tokenTypeDimensionValue"
}
]
},
"$type": {
"const": "dimension",
Expand Down Expand Up @@ -382,7 +408,14 @@
"minItems": 1,
"maxItems": 4,
"items": {
"type": "string"
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/tokenTypeDimensionValue"
}
]
}
},
"lineCap": {
Expand Down Expand Up @@ -428,11 +461,20 @@
"type": "object",
"properties": {
"color": { "type": "string" },
"width": { "type": "string" },
"width": {
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/tokenTypeDimensionValue"
}
]
},
"style": {
"allOf": [
"oneOf": [
{ "$ref": "#/definitions/tokenTypeStrokeStyleValue" },
{ "type": "string" }
{ "$ref": "#/definitions/tokenReference" }
]
}
},
Expand Down Expand Up @@ -490,16 +532,48 @@
"type": "string"
},
"offsetX": {
"type": "string"
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/tokenTypeDimensionValue"
}
]
},
"offsetY": {
"type": "string"
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/tokenTypeDimensionValue"
}
]
},
"blur": {
"type": "string"
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/tokenTypeDimensionValue"
}
]
},
"spread": {
"type": "string"
"oneOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/tokenTypeDimensionValue"
}
]
},
"inset": {
"type": "boolean",
"default": false
}
},
"required": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,16 @@ export interface DesignTokenTypeString extends DesignTokenBase<string> {
$type: 'string';
}

/** Value of a Design Token Dimension */
export interface DesignTokenTypeDimensionValue {
/** An integer or floating-point value representing the numeric value */
value: number;
/** Unit of distance */
unit: string;
}

/** Design Token Dimension */
export interface DesignTokenTypeDimension extends DesignTokenBase<string> {
export interface DesignTokenTypeDimension extends DesignTokenBase<string | DesignTokenTypeDimensionValue> {
/** @inheritdoc */
$type: 'dimension';
}
Expand Down Expand Up @@ -125,7 +133,7 @@ export interface DesignTokenTypeNumber extends DesignTokenBase<number | string>
}

type DesignTokenTypeStrokeStyleDetailsValue = {
dashArray: string[];
dashArray: (string | DesignTokenTypeDimensionValue)[];
lineCap: 'round' | 'butt' | 'square';
};

Expand All @@ -143,7 +151,7 @@ export interface DesignTokenTypeStrokeStyle<T extends DesignTokenTypeStrokeStyle

type DesignTokenTypeBorderValue = {
color: string;
width: string;
width: string | DesignTokenTypeDimensionValue;
style: DesignTokenTypeStrokeStyleValue;
};

Expand All @@ -168,10 +176,11 @@ export interface DesignTokenTypeTransition extends DesignTokenBase<DesignTokenTy

type DesignTokenTypeShadowValue = {
color: string;
offsetX: string;
offsetY: string;
blur: string;
spread: string;
offsetX: string | DesignTokenTypeDimensionValue;
offsetY: string | DesignTokenTypeDimensionValue;
blur: string | DesignTokenTypeDimensionValue;
spread: string | DesignTokenTypeDimensionValue;
inset?: boolean;
};

/** Design Token Shadow */
Expand Down Expand Up @@ -204,8 +213,8 @@ export interface DesignTokenTypeGradient extends DesignTokenBase<DesignTokenType

type DesignTokenTypeTypographyValue = {
fontFamily: string;
fontSize: string;
letterSpacing: string;
fontSize: string | DesignTokenTypeDimensionValue;
letterSpacing: string | DesignTokenTypeDimensionValue;
fontWeight: DesignTokenTypeFontWeightValue;
lineHeight: string | number;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,31 @@ describe('Design Token Parser', () => {
expect(gradient.getCssRawValue()).toBe('linear-gradient(180deg, #fff 10px)');
});

test('should generate correctly the border', () => {
const result = parser.parseDesignToken(exampleVariable);
const border = result.get('example.test.border-complex');

expect(border).toBeDefined();
expect(border.getType()).toBe('border');
expect(border.getCssRawValue()).toBe('2px square 1px red');
});

test('should generate correctly the store style', () => {
const result = parser.parseDesignToken(exampleVariable);
const stroke = result.get('example.test.stroke');

expect(stroke).toBeDefined();
expect(stroke.getCssRawValue()).toBe('dotted');
});

test('should generate correctly the store complex style', () => {
const result = parser.parseDesignToken(exampleVariable);
const stroke = result.get('example.test.stroke-complex');

expect(stroke).toBeDefined();
expect(stroke.getCssRawValue()).toBe('round 2px');
});

describe('should generate correctly the shadow', () => {
test('with single parameter', () => {
const result = parser.parseDesignToken(exampleVariable);
Expand All @@ -209,13 +234,22 @@ describe('Design Token Parser', () => {
expect(shadow.getCssRawValue()).toBe('1px 1px 1 1 #000');
});

test('with dimension parameter', () => {
const result = parser.parseDesignToken(exampleVariable);
const shadow = result.get('example.test.shadow-dimension');

expect(shadow).toBeDefined();
expect(shadow.getType()).toBe('shadow');
expect(shadow.getCssRawValue()).toBe('1px 1px 1 1 #000');
});

test('with multiple parameter', () => {
const result = parser.parseDesignToken(exampleVariable);
const shadow = result.get('example.test.shadow-multi');

expect(shadow).toBeDefined();
expect(shadow.getType()).toBe('shadow');
expect(shadow.getCssRawValue()).toBe('1px 1px 1 1 #000, 2px 2px 2 2 #fff');
expect(shadow.getCssRawValue()).toBe('inset 1px 1px 1 1 #000, 2px 2px 2 2 #fff');
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
DesignTokenMetadata,
DesignTokenNode,
DesignTokenSpecification,
DesignTokenTypeDimensionValue,
} from '../design-token-specification.interface';
import {
DesignTokenTypeStrokeStyleValue,
Expand Down Expand Up @@ -55,31 +56,27 @@ const getExtensions = (nodes: NodeReference[], context: DesignTokenContext | und
}, {} as DesignTokenGroupExtensions & DesignTokenExtensions);
};
const getReferences = (cssRawValue: string) => Array.from(cssRawValue.matchAll(tokenReferenceRegExp)).map(([,tokenRef]) => tokenRef);
const applyConversion = (token: DesignTokenVariableStructure, value: string) => {
if (typeof token.extensions.o3rUnit === 'undefined' || typeof token.extensions.o3rRatio === 'undefined') {
return value;
const applyConversion = (token: DesignTokenVariableStructure, value: string | number | DesignTokenTypeDimensionValue) => {
const o3rRatio = token.extensions.o3rRatio ?? 1;
const o3rUnit = token.extensions.o3rUnit;

if (typeof value === 'object') {
return Number.parseFloat((value.value * o3rRatio).toFixed(3)).toString() + (o3rUnit ?? (value.unit || ''));
}

const splitValue = splitValueNumericRegExp.exec(value);
const splitValue = splitValueNumericRegExp.exec(value.toString());
if (!splitValue) {
return value;
}

const [, floatValue, unit] = splitValue;
const newValue = Number.parseFloat((Number.parseFloat(floatValue) * o3rRatio).toFixed(3));

const newValue = value.replace(floatValue, (Number.parseFloat((Number.parseFloat(floatValue) * token.extensions.o3rRatio).toFixed(3))).toString());

if (unit) {
return newValue.replace(unit, token.extensions.o3rUnit);
}

if (floatValue === value) {
return newValue + token.extensions.o3rUnit;
}

return newValue;
return newValue + (o3rUnit ?? (unit || ''));
};
const renderCssTypeStrokeStyleValue = (value: DesignTokenTypeStrokeStyleValue) => isTokenTypeStrokeStyleValueComplex(value) ? `${value.lineCap} ${value.dashArray.join(' ')}` : value;
const renderCssTypeStrokeStyleValue = (token: DesignTokenVariableStructure, value: DesignTokenTypeStrokeStyleValue) => isTokenTypeStrokeStyleValueComplex(value)
? `${value.lineCap} ${value.dashArray.map((dashArrayValue) => applyConversion(token, dashArrayValue)).join(' ')}`
: value;
const sanitizeStringValue = (value: string) => value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
const sanitizeKeyName = (name: string) => name.replace(/[ .]+/g, '-').replace(/[()[\]]+/g, '');
const getCssRawValue = (variableSet: DesignTokenVariableSet, token: DesignTokenVariableStructure) => {
Expand All @@ -103,10 +100,10 @@ const getCssRawValue = (variableSet: DesignTokenVariableSet, token: DesignTokenV
case 'fontWeight':
case 'fontFamily':
case 'dimension': {
return applyConversion(token, checkNode.$value.toString());
return applyConversion(token, checkNode.$value);
}
case 'strokeStyle': {
return renderCssTypeStrokeStyleValue(checkNode.$value);
return renderCssTypeStrokeStyleValue(token, checkNode.$value);
}
case 'cubicBezier': {
return typeof checkNode.$value === 'string'
Expand All @@ -118,7 +115,7 @@ const getCssRawValue = (variableSet: DesignTokenVariableSet, token: DesignTokenV
case 'border': {
return typeof checkNode.$value === 'string'
? checkNode.$value
: `${applyConversion(token, checkNode.$value.width)} ${renderCssTypeStrokeStyleValue(checkNode.$value.style)} ${checkNode.$value.color}`;
: `${applyConversion(token, checkNode.$value.width)} ${renderCssTypeStrokeStyleValue(token, checkNode.$value.style)} ${checkNode.$value.color}`;
}
case 'gradient': {
if (typeof checkNode.$value === 'string') {
Expand All @@ -136,8 +133,10 @@ const getCssRawValue = (variableSet: DesignTokenVariableSet, token: DesignTokenV

const values = Array.isArray(checkNode.$value) ? checkNode.$value : [checkNode.$value];
return values
.map((value) => `${applyConversion(token, value.offsetX)} ${applyConversion(token, value.offsetY)} ${applyConversion(token, value.blur)} ${applyConversion(token, value.spread)}`
+ ` ${value.color}`)
.map((value) =>
(value.inset ? 'inset ' : '')
+ `${applyConversion(token, value.offsetX)} ${applyConversion(token, value.offsetY)} ${applyConversion(token, value.blur)} ${applyConversion(token, value.spread)}`
+ ` ${value.color}`)
.join(', ');
}
case 'transition': {
Expand Down
Loading

0 comments on commit 2c537d9

Please sign in to comment.