Skip to content

Commit 3306d71

Browse files
feat(css): add CSS utility classes to the ionic theme bundle to match other themes (#29974)
The default (iOS/MD) bundle is removed from the tests for the `ionic` theme because it adds global component styles that the `ionic` theme does not need. The missing utility files are imported, and padding/margin classes are generated from the design tokens, as many tests rely on `ion-padding` and `ion-text-center` being available. This change ensures the `ionic` theme includes the same classes offered in our documentation: https://ionicframework.com/docs/layout/css-utilities.
1 parent a5a7bee commit 3306d71

File tree

67 files changed

+325
-127
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+325
-127
lines changed

core/scripts/testing/scripts.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
linkTag.setAttribute('href', '/css/ionic/bundle.ionic.css');
4848
document.head.appendChild(linkTag);
4949
}
50+
51+
const defaultThemeLinkTag = document.querySelector('link[href*="css/ionic.bundle.css"]');
52+
if (defaultThemeLinkTag) {
53+
defaultThemeLinkTag.remove();
54+
}
5055
}
5156

5257
window.Ionic = window.Ionic || {};

core/scripts/tokens/index.js

Lines changed: 130 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
generateTypographyOutput,
1313
generateValue,
1414
generateColorUtilityClasses,
15+
generateDefaultSpaceUtilityClasses,
1516
generateSpaceUtilityClasses,
1617
removeConsecutiveRepeatedWords,
1718
setPrefixValue,
@@ -23,136 +24,138 @@
2324
} = require('./utils.js');
2425

2526
const StyleDictionary = (await import('style-dictionary')).default;
26-
27+
2728
// Set the prefix for variables and classes
2829
setPrefixValue('ion');
2930

3031
// Register a custom file header
3132
StyleDictionary.registerFileHeader({
32-
name: 'custom-header',
33-
fileHeader: async (defaultMessages = []) => {
34-
return [...defaultMessages, 'Do not edit directly, this file was auto-generated.'];
35-
},
33+
name: 'custom-header',
34+
fileHeader: async (defaultMessages = []) => {
35+
return [...defaultMessages, 'Do not edit directly, this file was auto-generated.'];
36+
},
3637
});
3738

3839
// SCSS variables format
3940
StyleDictionary.registerFormat({
4041
name: 'scssVariablesFormat',
41-
format: async function({ dictionary, file}) {
42+
format: async function ({ dictionary, file }) {
4243

4344
console.log('Generating SCSS variables...');
4445

45-
// Separate typography tokens from the rest
46-
const typographyProperties = dictionary.allTokens.filter((prop) => prop.$type === 'typography');
47-
const otherProperties = dictionary.allTokens.filter((prop) => prop.$type !== 'typography');
48-
49-
// Make sure the reused scss variables are defined first, to avoid compilation errors
50-
const sortedProperties = [...otherProperties, ...typographyProperties];
51-
52-
const prefixedVariables = sortedProperties.map((prop) => {
53-
// Remove consecutive repeated words from the token name, like border-border-color
54-
const propName = removeConsecutiveRepeatedWords(prop.name);
55-
56-
switch (prop.$type) {
57-
case 'boxShadow':
58-
return generateShadowValue(prop, propName);
59-
case 'fontFamilies':
60-
return generateFontFamilyValue(prop, propName, 'scss');
61-
case 'fontSizes':
62-
return generateFontSizeValue(prop, propName, 'scss');
63-
case 'typography':
64-
return generateTypographyOutput(prop, propName, true);
65-
default:
66-
return generateValue(prop, propName);
67-
}
68-
});
69-
70-
const fileHeader = await file.options.fileHeader();
71-
72-
return [
73-
`/*\n${fileHeader.join('\n')}\n*/`,
74-
'@use "../themes/functions.sizes" as font;\n',
75-
prefixedVariables.join('\n') + '\n',
76-
].join('\n');
46+
// Separate typography tokens from the rest
47+
const typographyProperties = dictionary.allTokens.filter((prop) => prop.$type === 'typography');
48+
const otherProperties = dictionary.allTokens.filter((prop) => prop.$type !== 'typography');
49+
50+
// Make sure the reused scss variables are defined first, to avoid compilation errors
51+
const sortedProperties = [...otherProperties, ...typographyProperties];
52+
53+
const prefixedVariables = sortedProperties.map((prop) => {
54+
// Remove consecutive repeated words from the token name, like border-border-color
55+
const propName = removeConsecutiveRepeatedWords(prop.name);
56+
57+
switch (prop.$type) {
58+
case 'boxShadow':
59+
return generateShadowValue(prop, propName);
60+
case 'fontFamilies':
61+
return generateFontFamilyValue(prop, propName, 'scss');
62+
case 'fontSizes':
63+
return generateFontSizeValue(prop, propName, 'scss');
64+
case 'typography':
65+
return generateTypographyOutput(prop, propName, true);
66+
default:
67+
return generateValue(prop, propName);
68+
}
69+
});
70+
71+
const fileHeader = await file.options.fileHeader();
72+
73+
return [
74+
`/*\n${fileHeader.join('\n')}\n*/`,
75+
'@use "../themes/functions.sizes" as font;\n',
76+
prefixedVariables.join('\n') + '\n',
77+
].join('\n');
7778
},
7879
});
7980

8081
// Create utility-classes
8182
StyleDictionary.registerFormat({
8283
name: 'cssUtilityClassesFormat',
83-
format: async function({ dictionary, file}) {
84-
85-
console.log('Generating Utility-Classes...');
86-
87-
// Arrays to store specific utility classes
88-
const typographyUtilityClasses = [];
89-
const otherUtilityClasses = [];
90-
91-
// Generate utility classes for each token
92-
dictionary.allTokens.map((prop) => {
93-
94-
const tokenCategory = prop.attributes.category;
95-
96-
if (prop.$type === 'fontFamilies' || tokenCategory === 'scale' || tokenCategory === 'backdrop') {
97-
// Not creating for the tokens below, as they make no sense to exist as utility-classes.
98-
return;
99-
}
100-
101-
// Remove consecutive repeated words from the token name, like border-border-color
102-
const propName = removeConsecutiveRepeatedWords(prop.name);
103-
104-
if (prop.$type === 'typography') {
105-
// Typography tokens are handled differently, as each might have a different tokenType
106-
return typographyUtilityClasses.push(generateTypographyOutput(prop, propName, false));
107-
108-
} else if(tokenCategory.startsWith('round') || tokenCategory.startsWith('rectangular') || tokenCategory.startsWith('soft')) {
109-
// Generate utility classes for border-radius shape tokens, as they have their own token json file, based on primitive tokens
110-
return otherUtilityClasses.push(generateRadiusUtilityClasses(propName));
111-
112-
}
113-
114-
let utilityClass = '';
115-
116-
switch (tokenCategory) {
117-
case 'color':
118-
case 'primitives':
119-
case 'semantics':
120-
case 'text':
121-
case 'bg':
122-
case 'icon':
123-
case 'state':
124-
utilityClass = generateColorUtilityClasses(prop, propName);
125-
break;
126-
case 'border-size':
127-
utilityClass = generateBorderSizeUtilityClasses(propName);
128-
break;
129-
case 'font':
130-
utilityClass = generateFontUtilityClasses(prop, propName);
131-
break;
132-
case 'space':
133-
utilityClass = generateSpaceUtilityClasses(prop, propName);
134-
break;
135-
case 'shadow':
136-
case 'elevation':
137-
utilityClass = generateShadowUtilityClasses(propName);
138-
break;
139-
default:
140-
utilityClass = generateUtilityClasses(tokenCategory, propName);
141-
}
142-
143-
return otherUtilityClasses.push(utilityClass);
144-
});
145-
146-
// Concatenate typography utility classes at the beginning
147-
const finalOutput = typographyUtilityClasses.concat(otherUtilityClasses).join('\n');
148-
149-
const fileHeader = await file.options.fileHeader();
150-
151-
return [
152-
`/*\n${fileHeader.join('\n')}\n*/`,
153-
'@import "./ionic.vars";\n@import "../themes/mixins";\n',
154-
finalOutput,
155-
].join('\n');
84+
format: async function ({ dictionary, file }) {
85+
86+
console.log('Generating Utility-Classes...');
87+
88+
// Arrays to store specific utility classes
89+
const typographyUtilityClasses = [];
90+
const otherUtilityClasses = [];
91+
92+
// Generate utility classes for each token
93+
dictionary.allTokens.map((prop) => {
94+
95+
const tokenCategory = prop.attributes.category;
96+
97+
if (prop.$type === 'fontFamilies' || tokenCategory === 'scale' || tokenCategory === 'backdrop') {
98+
// Not creating for the tokens below, as they make no sense to exist as utility-classes.
99+
return;
100+
}
101+
102+
// Remove consecutive repeated words from the token name, like border-border-color
103+
const propName = removeConsecutiveRepeatedWords(prop.name);
104+
105+
if (prop.$type === 'typography') {
106+
// Typography tokens are handled differently, as each might have a different tokenType
107+
return typographyUtilityClasses.push(generateTypographyOutput(prop, propName, false));
108+
109+
} else if (tokenCategory.startsWith('round') || tokenCategory.startsWith('rectangular') || tokenCategory.startsWith('soft')) {
110+
// Generate utility classes for border-radius shape tokens, as they have their own token json file, based on primitive tokens
111+
return otherUtilityClasses.push(generateRadiusUtilityClasses(propName));
112+
}
113+
114+
let utilityClass = '';
115+
116+
switch (tokenCategory) {
117+
case 'color':
118+
case 'primitives':
119+
case 'semantics':
120+
case 'text':
121+
case 'bg':
122+
case 'icon':
123+
case 'state':
124+
utilityClass = generateColorUtilityClasses(prop, propName);
125+
break;
126+
case 'border-size':
127+
utilityClass = generateBorderSizeUtilityClasses(propName);
128+
break;
129+
case 'font':
130+
utilityClass = generateFontUtilityClasses(prop, propName);
131+
break;
132+
case 'space':
133+
utilityClass = generateSpaceUtilityClasses(prop, propName);
134+
break;
135+
case 'shadow':
136+
case 'elevation':
137+
utilityClass = generateShadowUtilityClasses(propName);
138+
break;
139+
default:
140+
utilityClass = generateUtilityClasses(tokenCategory, propName);
141+
}
142+
143+
return otherUtilityClasses.push(utilityClass);
144+
});
145+
146+
const defaultSpaceUtilityClasses = generateDefaultSpaceUtilityClasses();
147+
otherUtilityClasses.push(defaultSpaceUtilityClasses);
148+
149+
// Concatenate typography utility classes at the beginning
150+
const finalOutput = typographyUtilityClasses.concat(otherUtilityClasses).join('\n');
151+
152+
const fileHeader = await file.options.fileHeader();
153+
154+
return [
155+
`/*\n${fileHeader.join('\n')}\n*/`,
156+
'@import "./ionic.vars";\n@import "../themes/mixins";\n',
157+
finalOutput,
158+
].join('\n');
156159
},
157160
});
158161

@@ -163,24 +166,24 @@ module.exports = {
163166
source: ["node_modules/outsystems-design-tokens/tokens/**/*.json"],
164167
platforms: {
165168
scss: {
166-
transformGroup: "scss",
167-
buildPath: './src/foundations/',
168-
files: [
169-
{
170-
destination: "ionic.vars.scss",
171-
format: "scssVariablesFormat",
172-
options: {
173-
fileHeader: `custom-header`,
174-
},
169+
transformGroup: "scss",
170+
buildPath: './src/foundations/',
171+
files: [
172+
{
173+
destination: "ionic.vars.scss",
174+
format: "scssVariablesFormat",
175+
options: {
176+
fileHeader: `custom-header`,
175177
},
176-
{
177-
destination: "ionic.utility.scss",
178-
format: "cssUtilityClassesFormat",
179-
options: {
180-
fileHeader: `custom-header`
181-
}
178+
},
179+
{
180+
destination: "ionic.utility.scss",
181+
format: "cssUtilityClassesFormat",
182+
options: {
183+
fileHeader: `custom-header`
182184
}
183-
]
185+
}
186+
]
184187
}
185188
}
186189
};

core/scripts/tokens/utils.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,73 @@ function generateColorUtilityClasses(prop, className) {
125125
.${variablesPrefix}-background-${className} {\n background-color: $${variablesPrefix}-${prop.name};\n}`;
126126
}
127127

128+
// Generates margin and padding utility classes to match the token-agnostic
129+
// utilities provided by the Ionic Framework
130+
function generateDefaultSpaceUtilityClasses() {
131+
const zeroMarginPaddingToken = 'space-0';
132+
const defaultMarginPaddingToken = 'space-400';
133+
134+
const marginPaddingTemplate = (type) => `
135+
.${variablesPrefix}-no-${type} {
136+
--${type}-top: #{$${variablesPrefix}-${zeroMarginPaddingToken}};
137+
--${type}-end: #{$${variablesPrefix}-${zeroMarginPaddingToken}};
138+
--${type}-bottom: #{$${variablesPrefix}-${zeroMarginPaddingToken}};
139+
--${type}-start: #{$${variablesPrefix}-${zeroMarginPaddingToken}};
140+
141+
@include ${type}($${variablesPrefix}-${zeroMarginPaddingToken});
142+
};
143+
144+
.${variablesPrefix}-${type} {
145+
--${type}-top: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
146+
--${type}-end: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
147+
--${type}-bottom: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
148+
--${type}-start: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
149+
150+
@include ${type}($${variablesPrefix}-${defaultMarginPaddingToken});
151+
};
152+
153+
.${variablesPrefix}-${type}-top {
154+
--${type}-top: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
155+
156+
@include ${type}($${variablesPrefix}-${defaultMarginPaddingToken}, null, null, null);
157+
};
158+
159+
.${variablesPrefix}-${type}-end {
160+
--${type}-end: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
161+
162+
@include ${type}(null, $${variablesPrefix}-${defaultMarginPaddingToken}, null, null);
163+
};
164+
165+
.${variablesPrefix}-${type}-bottom {
166+
--${type}-bottom: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
167+
168+
@include ${type}(null, null, $${variablesPrefix}-${defaultMarginPaddingToken}, null);
169+
};
170+
171+
.${variablesPrefix}-${type}-start {
172+
--${type}-start: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
173+
174+
@include ${type}(null, null, null, $${variablesPrefix}-${defaultMarginPaddingToken});
175+
};
176+
177+
.${variablesPrefix}-${type}-vertical {
178+
--${type}-top: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
179+
--${type}-bottom: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
180+
181+
@include ${type}($${variablesPrefix}-${defaultMarginPaddingToken}, null, $${variablesPrefix}-${defaultMarginPaddingToken}, null);
182+
};
183+
184+
.${variablesPrefix}-${type}-horizontal {
185+
--${type}-start: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
186+
--${type}-end: #{$${variablesPrefix}-${defaultMarginPaddingToken}};
187+
188+
@include ${type}(null, $${variablesPrefix}-${defaultMarginPaddingToken}, null, $${variablesPrefix}-${defaultMarginPaddingToken});
189+
};
190+
`;
191+
192+
return `${marginPaddingTemplate('margin')}\n${marginPaddingTemplate('padding')}`;
193+
}
194+
128195
// Generates a margin or padding based css utility-class from a space Design Token structure
129196
function generateSpaceUtilityClasses(prop, className) {
130197
// This exact format is needed so that it compiles the tokens with the expected lint rules
@@ -203,6 +270,7 @@ module.exports = {
203270
setPrefixValue,
204271
generateRadiusUtilityClasses,
205272
generateColorUtilityClasses,
273+
generateDefaultSpaceUtilityClasses,
206274
generateSpaceUtilityClasses,
207275
removeConsecutiveRepeatedWords,
208276
generateBorderSizeUtilityClasses,

0 commit comments

Comments
 (0)