@baloise/ds-tokens 20.0.0-next.7 → 20.0.0-next.9

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.
@@ -5,7 +5,7 @@ const basePxFontSize = 16;
5
5
  * `$value` differs from the base. Keeps the full hierarchy so Style
6
6
  * Dictionary can still resolve references.
7
7
  */
8
- function computeTokenDiff(base, brand) {
8
+ export function computeTokenDiff(base, brand) {
9
9
  const result = {};
10
10
  for (const [key, brandVal] of Object.entries(brand)) {
11
11
  if (typeof brandVal !== 'object' || brandVal === null)
@@ -0,0 +1,26 @@
1
+ import { kebabCase } from 'change-case';
2
+ /**
3
+ * Turns a token's path into its final CSS custom property name, matching Style Dictionary's
4
+ * `css` transformGroup (`name/kebab`, which itself uses `change-case`'s `kebabCase`) followed by
5
+ * the `ds/css/name` transform (`transformers.ts`). Returns the bare name (no leading `--`), the
6
+ * same shape Style Dictionary expects from a `name`-type transform - the SD transform is a thin
7
+ * wrapper around this function, and `apps/toky`'s live preview imports it directly so both stay
8
+ * in sync (see ADR-0021).
9
+ */
10
+ export const tokenNameToCssVar = (pathSegments, prefix = 'ds') => {
11
+ let tokenName = kebabCase([prefix, ...pathSegments].join(' '));
12
+ const isComponent = pathSegments.includes('🧩 Component');
13
+ if (isComponent) {
14
+ tokenName = tokenName.replace('-component', '');
15
+ }
16
+ // we use t-shirt sizes and need to make sure that 2xl becomes 2xl and not 2-xl
17
+ // check if token names has number followed by xl or xs and replace it with numberxl without dash
18
+ if (/-?([0-9]+)-(xl|xs)/.test(tokenName)) {
19
+ tokenName = tokenName.replace(/-?([0-9]+)-(xl|xs)/, '-$1$2');
20
+ }
21
+ // check that no name has double dash and replace it with single dash
22
+ if (tokenName.includes('--')) {
23
+ tokenName = tokenName.replace(/--+/g, '-');
24
+ }
25
+ return tokenName;
26
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Public subpath (`@baloise/ds-tokens/css-preview`) for consumers outside the build pipeline —
3
+ * currently `apps/toky`'s live token preview — that need to turn a token path/resolved value into
4
+ * the same CSS the build actually produces, without duplicating the Style Dictionary transforms.
5
+ * See ADR-0021.
6
+ */
7
+ export { tokenNameToCssVar } from './css-naming.js';
8
+ export { resolvedValueToCss } from './css-value.js';
@@ -0,0 +1,86 @@
1
+ import { tokenNameToCssVar } from './css-naming.js';
2
+ /**
3
+ * Mirrors the `ds/color/rgba` transform: DTCG color values already carry a `hex` field in this
4
+ * token set (see `tokens/Base.tokens.json`), so this only needs to add an `rgba()` wrapper when
5
+ * `alpha < 1`.
6
+ */
7
+ export const colorValueToCss = (value) => {
8
+ if (typeof value !== 'object' || value === null || !('hex' in value)) {
9
+ return null;
10
+ }
11
+ const { hex, alpha } = value;
12
+ if (typeof hex !== 'string') {
13
+ return null;
14
+ }
15
+ if (typeof alpha === 'number' && alpha < 1) {
16
+ const stripped = hex.replace('#', '');
17
+ const r = parseInt(stripped.substring(0, 2), 16);
18
+ const g = parseInt(stripped.substring(2, 4), 16);
19
+ const b = parseInt(stripped.substring(4, 6), 16);
20
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
21
+ }
22
+ return hex;
23
+ };
24
+ const ROUND_TOKEN_NAME_MARKERS = ['line-height', 'lineheight', 'opacity'];
25
+ /**
26
+ * Mirrors `ds/size/round`. Case-insensitive because callers pass different name casings
27
+ * depending on the platform's name transform - kebab-case for CSS/SCSS (`ds/css/name`), PascalCase
28
+ * for JS (`ds/js/name`).
29
+ */
30
+ export const roundNumberValue = (value, tokenName) => {
31
+ const lowerName = tokenName.toLowerCase();
32
+ if (ROUND_TOKEN_NAME_MARKERS.some(marker => lowerName.includes(marker))) {
33
+ return Math.round(value * 10) / 10;
34
+ }
35
+ return value;
36
+ };
37
+ const NUMBER_ONLY_PATH_MARKERS = [
38
+ 'LineHeight',
39
+ 'FontWeight',
40
+ 'Opacity',
41
+ '🌫️ Opacity',
42
+ 'ZIndex',
43
+ 'Z-Index',
44
+ '🗂️ ZIndex',
45
+ '🗂️ Z-Index',
46
+ 'Interaction',
47
+ '✨ Interaction',
48
+ ];
49
+ const PIXEL_PATH_MARKERS = ['📐 Breakpoint', 'Breakpoint', '🗃️ Container', 'Container'];
50
+ /** Mirrors `ds/size/rem`: converts a px number into rem, unless the token's path says otherwise. */
51
+ export const numberValueToCssSize = (value, path) => {
52
+ if (`${value}`.endsWith('px') || `${value}`.endsWith('rem')) {
53
+ return value;
54
+ }
55
+ if (NUMBER_ONLY_PATH_MARKERS.some(marker => path.includes(marker))) {
56
+ return Math.round(value * 10) / 10;
57
+ }
58
+ if (PIXEL_PATH_MARKERS.some(marker => path.includes(marker))) {
59
+ return `${value}px`;
60
+ }
61
+ if (value === 9999) {
62
+ return `${value}px`;
63
+ }
64
+ return `${value / 16}rem`;
65
+ };
66
+ /**
67
+ * Turns a resolved DTCG token value into a CSS-ready string, for the live token preview
68
+ * (`apps/toky`) to send as a `--ds-*` custom property value. Mirrors the `ds/color/rgba` /
69
+ * `ds/size/round` / `ds/size/rem` Style Dictionary transforms via the functions above, so the
70
+ * preview never drifts from what the build actually produces (see ADR-0021). Returns `null` for
71
+ * value shapes it doesn't recognize, so the caller can skip that token rather than send garbage.
72
+ */
73
+ export const resolvedValueToCss = (value, type, path) => {
74
+ if (type === 'color') {
75
+ return colorValueToCss(value);
76
+ }
77
+ if (type === 'number' && typeof value === 'number') {
78
+ const cssVarName = tokenNameToCssVar(path);
79
+ const rounded = roundNumberValue(value, cssVarName);
80
+ return `${numberValueToCssSize(rounded, path)}`;
81
+ }
82
+ if (type === 'string' && typeof value === 'string') {
83
+ return value;
84
+ }
85
+ return null;
86
+ };
@@ -100,7 +100,7 @@ export const registerCustomFormatters = (sd) => {
100
100
  // `\n}\n` +
101
101
  // '\n'
102
102
  return (header +
103
- ':root {\n' +
103
+ ':root, :host {\n' +
104
104
  formattedVariables({
105
105
  format: propertyFormatNames.css,
106
106
  dictionary,
@@ -126,7 +126,7 @@ export const registerCustomFormatters = (sd) => {
126
126
  '\n}\n\n' +
127
127
  '/* Device tokens: Tablet */\n' +
128
128
  `\n@media (min-width: 769px) {\n` +
129
- ':root {\n' +
129
+ ':root, :host {\n' +
130
130
  formattedVariables({
131
131
  format: propertyFormatNames.css,
132
132
  dictionary: deviceTabletDictionary,
@@ -137,7 +137,7 @@ export const registerCustomFormatters = (sd) => {
137
137
  `\n}\n\n` +
138
138
  '/* Device tokens: Desktop */\n' +
139
139
  `\n@media (min-width: 1024px) {\n` +
140
- ':root {\n' +
140
+ ':root, :host {\n' +
141
141
  formattedVariables({
142
142
  format: propertyFormatNames.css,
143
143
  dictionary: deviceDesktopDictionary,
@@ -1,3 +1,5 @@
1
+ import { colorValueToCss, numberValueToCssSize, roundNumberValue } from './css-value.js';
2
+ import { tokenNameToCssVar } from './css-naming.js';
1
3
  export const registerCustomTransformers = (sd) => {
2
4
  /**
3
5
  * Transform color tokens with hex and alpha properties to rgba() format
@@ -9,19 +11,7 @@ export const registerCustomTransformers = (sd) => {
9
11
  filter: token => token.$type === 'color',
10
12
  transform: token => {
11
13
  const value = token.$value ?? token.value;
12
- // Handle object values with hex and alpha properties
13
- if (typeof value === 'object' && value !== null && 'hex' in value && 'alpha' in value && value.alpha < 1) {
14
- const hex = value.hex.replace('#', '');
15
- const r = parseInt(hex.substring(0, 2), 16);
16
- const g = parseInt(hex.substring(2, 4), 16);
17
- const b = parseInt(hex.substring(4, 6), 16);
18
- const a = parseFloat(value.alpha);
19
- return `rgba(${r}, ${g}, ${b}, ${a})`;
20
- }
21
- if (value.hex) {
22
- return value.hex;
23
- }
24
- return value;
14
+ return colorValueToCss(value) ?? value;
25
15
  },
26
16
  });
27
17
  /**
@@ -48,23 +38,7 @@ export const registerCustomTransformers = (sd) => {
48
38
  type: `name`,
49
39
  transitive: true,
50
40
  name: `ds/css/name`,
51
- transform: token => {
52
- let tokenName = token.name;
53
- const isComponent = token.path.includes('🧩 Component');
54
- if (isComponent) {
55
- tokenName = tokenName.replace('-component', '');
56
- }
57
- // we use t-shirt sizes and need to make sure that 2xl becomes 2xl and not 2-xl
58
- // check if token names has number followed by xl or xs and replace it with numberxl without dash
59
- if (/-?([0-9]+)-(xl|xs)/.test(tokenName)) {
60
- tokenName = tokenName.replace(/-?([0-9]+)-(xl|xs)/, '-$1$2');
61
- }
62
- // check that no name has double dash and replace it with single dash
63
- if (tokenName.includes('--')) {
64
- tokenName = tokenName.replace(/--+/g, '-');
65
- }
66
- return tokenName;
67
- },
41
+ transform: (token, config) => tokenNameToCssVar(token.path, config.prefix ?? 'ds'),
68
42
  });
69
43
  /**
70
44
  * Transform token names for CSS usage
@@ -88,47 +62,7 @@ export const registerCustomTransformers = (sd) => {
88
62
  transitive: true,
89
63
  name: `ds/size/rem`,
90
64
  filter: token => token.$type === 'number',
91
- transform: token => {
92
- // const name = token.name
93
- const value = token.$value;
94
- // const originalValue = token.original.$value
95
- const path = token.path;
96
- // const isReference =
97
- // typeof originalValue === 'string' && originalValue.startsWith('{') && originalValue.endsWith('}')
98
- if (`${value}`.endsWith('px')) {
99
- return value;
100
- }
101
- if (`${value}`.endsWith('rem')) {
102
- return value;
103
- }
104
- // Number only values with no unit
105
- const tokenToBeNumberOnly = [
106
- 'LineHeight',
107
- 'FontWeight',
108
- 'Opacity',
109
- '🌫️ Opacity',
110
- 'ZIndex',
111
- 'Z-Index',
112
- '🗂️ ZIndex',
113
- '🗂️ Z-Index',
114
- 'Interaction',
115
- '✨ Interaction',
116
- ];
117
- if (tokenToBeNumberOnly.some(ignored => path.includes(ignored))) {
118
- return Math.round(value * 10) / 10;
119
- }
120
- // Number only values with no unit
121
- const tokenToBeNumberPixel = ['📐 Breakpoint', 'Breakpoint', '🗃️ Container', 'Container'];
122
- if (tokenToBeNumberPixel.some(ignored => path.includes(ignored))) {
123
- return value + 'px';
124
- }
125
- // Extra case for rounded radius
126
- if (value === 9999) {
127
- return value + 'px';
128
- }
129
- // turn pixel into rem
130
- return value / 16 + 'rem';
131
- },
65
+ transform: token => numberValueToCssSize(token.$value, token.path),
132
66
  });
133
67
  sd.registerTransform({
134
68
  type: `value`,
@@ -139,14 +73,7 @@ export const registerCustomTransformers = (sd) => {
139
73
  const value = token.$value ?? token.value;
140
74
  const name = token.name;
141
75
  const tokenName = Array.isArray(name) ? name.join('-') : String(name ?? '');
142
- // if line-height round the value to 1 decimal place
143
- if (tokenName.includes('line-height') ||
144
- tokenName.includes('opacity') ||
145
- tokenName.includes('LineHeight') ||
146
- tokenName.includes('Opacity')) {
147
- return Math.round(value * 10) / 10;
148
- }
149
- return value;
76
+ return roundNumberValue(value, tokenName);
150
77
  },
151
78
  });
152
79
  };